Skip to content

Commit 426868b

Browse files
committed
[2085] Group diagrams by view definition in the Views Explorer
Bug: #2085 Signed-off-by: Florian ROUËNÉ <florian.rouene@obeosoft.com>
1 parent 793fa8f commit 426868b

File tree

7 files changed

+345
-1
lines changed

7 files changed

+345
-1
lines changed

CHANGELOG.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@ Make sure diagnostics are sorted according to their validated element and its co
160160
- https://github.com/eclipse-syson/syson/issues/2057[#2057] [diagrams] Add the support for empty value for multiplicity in the ANTLR grammar.
161161
- https://github.com/eclipse-syson/syson/issues/2053[#2053] [diagrams] The graphical `ForkNode` and `JoinNode` now use `RectangularNodeStyleDescription` and are restricted to horizontal resizing.
162162
Consequently, their entire footprint is filled with black, ensuring that all incoming and outgoing edges maintain a valid connection point.
163+
- https://github.com/eclipse-syson/syson/issues/2085[#2085] [views-explorer] Adapt views explorer to group diagram representations by view definition.
164+
163165

164166
=== New features
165167

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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.views.explorer;
14+
15+
import java.util.AbstractMap;
16+
import java.util.List;
17+
import java.util.Map;
18+
import java.util.Objects;
19+
import java.util.Optional;
20+
import java.util.stream.Collectors;
21+
22+
import org.eclipse.sirius.components.core.api.IEditingContext;
23+
import org.eclipse.sirius.components.core.api.IObjectSearchService;
24+
import org.eclipse.sirius.components.core.api.IURLParser;
25+
import org.eclipse.sirius.components.diagrams.description.DiagramDescription;
26+
import org.eclipse.sirius.components.representations.IRepresentationDescription;
27+
import org.eclipse.sirius.web.application.representation.services.RepresentationSearchService;
28+
import org.eclipse.sirius.web.application.views.viewsexplorer.services.RepresentationDescriptionType;
29+
import org.eclipse.sirius.web.application.views.viewsexplorer.services.RepresentationKind;
30+
import org.eclipse.sirius.web.application.views.viewsexplorer.services.api.IViewsExplorerContentServiceDelegate;
31+
import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.RepresentationMetadata;
32+
import org.eclipse.syson.sysml.ViewDefinition;
33+
import org.eclipse.syson.sysml.ViewUsage;
34+
import org.springframework.stereotype.Service;
35+
36+
/**
37+
* Customize the retrieval of the content of the views explorer for SysON.
38+
*
39+
* @author frouene
40+
*/
41+
@Service
42+
public class SysONViewsExplorerContentServiceDelegate implements IViewsExplorerContentServiceDelegate {
43+
44+
private final IURLParser urlParser;
45+
46+
private final IObjectSearchService objectSearchService;
47+
48+
public SysONViewsExplorerContentServiceDelegate(IURLParser urlParser, RepresentationSearchService representationSearchService, IObjectSearchService objectSearchService) {
49+
this.urlParser = Objects.requireNonNull(urlParser);
50+
this.objectSearchService = objectSearchService;
51+
}
52+
53+
@Override
54+
public boolean canHandle(IEditingContext editingContext) {
55+
return true;
56+
}
57+
58+
@Override
59+
public List<RepresentationKind> getContents(IEditingContext editingContext, List<RepresentationMetadata> representationMetadata, Map<String, IRepresentationDescription> representationDescriptions) {
60+
var descriptionTypes = this.groupByDescriptionType(editingContext, representationMetadata, representationDescriptions);
61+
return this.groupByKind(descriptionTypes);
62+
}
63+
64+
private List<RepresentationDescriptionType> groupByDescriptionType(IEditingContext editingContext, List<RepresentationMetadata> allMetadata,
65+
Map<String, IRepresentationDescription> allDescriptions) {
66+
var metadataToViewDefinitionMap = allMetadata.stream()
67+
.flatMap(metadata ->
68+
this.objectSearchService.getObject(editingContext, metadata.getTargetObjectId())
69+
.stream()
70+
.filter(ViewUsage.class::isInstance)
71+
.map(ViewUsage.class::cast)
72+
.map(ViewUsage::getViewDefinition)
73+
.map(viewDefinition -> new AbstractMap.SimpleEntry<>(metadata, viewDefinition))
74+
)
75+
.collect(Collectors.toMap(
76+
Map.Entry::getKey,
77+
Map.Entry::getValue,
78+
(existing, replacement) -> existing
79+
));
80+
81+
Map<ViewDefinition, List<RepresentationMetadata>> viewDefinitionToMetadataMap = allMetadata.stream()
82+
.collect(Collectors.groupingBy(metadataToViewDefinitionMap::get));
83+
84+
return viewDefinitionToMetadataMap.entrySet().stream()
85+
.map(entry -> {
86+
ViewDefinition viewDefinition = entry.getKey();
87+
String viewDefinitionName = viewDefinition.getDeclaredShortName();
88+
RepresentationMetadata firstMetadata = entry.getValue().get(0);
89+
String descriptionId = firstMetadata.getDescriptionId();
90+
IRepresentationDescription representationDescription = allDescriptions.get(descriptionId);
91+
92+
return Optional.ofNullable(representationDescription)
93+
.map(rd -> {
94+
if (rd instanceof DiagramDescription) {
95+
return new RepresentationDescriptionType(viewDefinitionName, rd, entry.getValue());
96+
}
97+
return new RepresentationDescriptionType(descriptionId, rd, entry.getValue());
98+
});
99+
})
100+
.flatMap(Optional::stream)
101+
.toList();
102+
}
103+
104+
private List<RepresentationKind> groupByKind(List<RepresentationDescriptionType> descriptionTypes) {
105+
return descriptionTypes.stream()
106+
.collect(Collectors.groupingBy(descType -> descType.representationsMetadata().get(0).getKind()))
107+
.entrySet().stream()
108+
.map(entry -> {
109+
var kindId = entry.getKey();
110+
var kindName = this.urlParser.getParameterValues(kindId).get("type").get(0);
111+
return new RepresentationKind(kindId, kindName, entry.getValue());
112+
})
113+
.toList();
114+
}
115+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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.views.explorer;
14+
15+
import java.util.List;
16+
import java.util.Objects;
17+
18+
import org.eclipse.sirius.components.collaborative.trees.api.IRenameTreeItemHandler;
19+
import org.eclipse.sirius.components.core.api.IEditingContext;
20+
import org.eclipse.sirius.components.core.api.ILabelService;
21+
import org.eclipse.sirius.components.core.api.IReadOnlyObjectPredicate;
22+
import org.eclipse.sirius.components.core.api.labels.StyledString;
23+
import org.eclipse.sirius.components.core.api.labels.StyledStringFragment;
24+
import org.eclipse.sirius.components.core.api.labels.StyledStringFragmentStyle;
25+
import org.eclipse.sirius.components.diagrams.description.DiagramDescription;
26+
import org.eclipse.sirius.components.representations.Failure;
27+
import org.eclipse.sirius.components.representations.IStatus;
28+
import org.eclipse.sirius.components.trees.Tree;
29+
import org.eclipse.sirius.components.trees.TreeItem;
30+
import org.eclipse.sirius.web.application.views.viewsexplorer.services.RepresentationDescriptionType;
31+
import org.eclipse.sirius.web.application.views.viewsexplorer.services.RepresentationKind;
32+
import org.eclipse.sirius.web.application.views.viewsexplorer.services.api.IViewsExplorerLabelServiceDelegate;
33+
import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.RepresentationMetadata;
34+
import org.eclipse.sirius.web.domain.services.api.IMessageService;
35+
import org.eclipse.syson.util.StandardDiagramsConstants;
36+
import org.springframework.stereotype.Service;
37+
38+
/**
39+
* Provide the behavior of the views explorer fro SysON.
40+
*
41+
* @author frouene
42+
*/
43+
@Service
44+
public class SysONViewsExplorerLabelServiceDelegate implements IViewsExplorerLabelServiceDelegate {
45+
46+
private final IReadOnlyObjectPredicate readOnlyObjectPredicate;
47+
48+
private final List<IRenameTreeItemHandler> renameTreeItemHandlers;
49+
50+
private final ILabelService labelService;
51+
52+
private final IMessageService messageService;
53+
54+
public SysONViewsExplorerLabelServiceDelegate(IReadOnlyObjectPredicate readOnlyObjectPredicate, List<IRenameTreeItemHandler> renameTreeItemHandlers, ILabelService labelService, IMessageService messageService) {
55+
this.readOnlyObjectPredicate = Objects.requireNonNull(readOnlyObjectPredicate);
56+
this.renameTreeItemHandlers = Objects.requireNonNull(renameTreeItemHandlers);
57+
this.labelService = Objects.requireNonNull(labelService);
58+
this.messageService = Objects.requireNonNull(messageService);
59+
}
60+
@Override
61+
public boolean canHandle(IEditingContext editingContext) {
62+
return true;
63+
}
64+
65+
@Override
66+
public boolean isEditable(Object self) {
67+
return !this.readOnlyObjectPredicate.test(self) && self instanceof RepresentationMetadata;
68+
}
69+
70+
@Override
71+
public StyledString getLabel(Object self) {
72+
var result = StyledString.of("");
73+
if (self instanceof RepresentationKind kind) {
74+
String name = kind.name();
75+
String size = String.valueOf(kind.representationDescriptionTypes().stream().mapToLong(descType -> descType.representationsMetadata().size()).sum());
76+
result = this.getColoredLabel(name, size);
77+
} else if (self instanceof RepresentationDescriptionType descriptionType) {
78+
String name = descriptionType.description().getLabel();
79+
if (descriptionType.description() instanceof DiagramDescription) {
80+
name = StandardDiagramsConstants.getValueFromShortName(descriptionType.id());
81+
}
82+
String size = String.valueOf(descriptionType.representationsMetadata().size());
83+
result = this.getColoredLabel(name, size);
84+
} else {
85+
result = this.labelService.getStyledLabel(self);
86+
}
87+
return result;
88+
}
89+
90+
@Override
91+
public IStatus editLabel(IEditingContext editingContext, Tree tree, TreeItem treeItem, String newValue) {
92+
var optionalHandler = this.renameTreeItemHandlers.stream()
93+
.filter(handler -> handler.canHandle(editingContext, treeItem, newValue))
94+
.findFirst();
95+
96+
if (optionalHandler.isPresent()) {
97+
IRenameTreeItemHandler renameTreeItemHandler = optionalHandler.get();
98+
return renameTreeItemHandler.handle(editingContext, treeItem, newValue, tree);
99+
}
100+
101+
return new Failure(this.messageService.failedToRename());
102+
}
103+
104+
private StyledString getColoredLabel(String label, String size) {
105+
return new StyledString(List.of(
106+
new StyledStringFragment("%s (%s)".formatted(label.toUpperCase(), size), StyledStringFragmentStyle.newDefaultStyledStringFragmentStyle()
107+
.foregroundColor("#261E588A")
108+
.build())
109+
));
110+
}
111+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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.views.view;
14+
15+
import static org.assertj.core.api.Assertions.assertThat;
16+
import static org.eclipse.sirius.components.trees.tests.TreeEventPayloadConsumer.assertRefreshedTreeThat;
17+
18+
import java.time.Duration;
19+
import java.util.List;
20+
import java.util.UUID;
21+
import java.util.function.Consumer;
22+
23+
import org.eclipse.sirius.components.diagrams.Diagram;
24+
import org.eclipse.sirius.components.tables.Table;
25+
import org.eclipse.sirius.web.application.views.viewsexplorer.ViewsExplorerEventInput;
26+
import org.eclipse.sirius.web.application.views.viewsexplorer.services.ViewsExplorerTreeDescriptionProvider;
27+
import org.eclipse.sirius.web.tests.graphql.ViewsExplorerEventSubscriptionRunner;
28+
import org.eclipse.sirius.web.tests.services.representation.RepresentationIdBuilder;
29+
import org.eclipse.syson.AbstractIntegrationTests;
30+
import org.eclipse.syson.GivenSysONServer;
31+
import org.eclipse.syson.application.data.InterconnectionViewEmptyTestProjectData;
32+
import org.eclipse.syson.application.data.RequirementsTableTestProjectData;
33+
import org.junit.jupiter.api.DisplayName;
34+
import org.junit.jupiter.api.Test;
35+
import org.springframework.beans.factory.annotation.Autowired;
36+
import org.springframework.boot.test.context.SpringBootTest;
37+
import org.springframework.transaction.annotation.Transactional;
38+
39+
import reactor.test.StepVerifier;
40+
41+
/**
42+
* Integration tests of the Views view.
43+
*
44+
* @author frouene
45+
*/
46+
@Transactional
47+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
48+
public class SysONViewsExplorerViewControllerIntegrationTests extends AbstractIntegrationTests {
49+
50+
@Autowired
51+
private ViewsExplorerEventSubscriptionRunner viewsExplorerEventSubscriptionRunner;
52+
53+
@DisplayName("GIVEN a project with a table, WHEN we subscribe to views events, THEN then the tree contains the table representation leaf")
54+
@GivenSysONServer({ RequirementsTableTestProjectData.SCRIPT_PATH })
55+
@Test
56+
public void viewsWithTableRepresentation() {
57+
var representationId = new RepresentationIdBuilder().buildViewsExplorerViewRepresentationId(
58+
List.of(Table.KIND));
59+
var defaultExplorerInput = new ViewsExplorerEventInput(UUID.randomUUID(), RequirementsTableTestProjectData.EDITING_CONTEXT_ID, representationId);
60+
var defaultFlux = this.viewsExplorerEventSubscriptionRunner.run(defaultExplorerInput).flux();
61+
62+
Consumer<Object> initialDefaultViewsContentConsumer = assertRefreshedTreeThat(tree -> {
63+
assertThat(tree).isNotNull();
64+
assertThat(tree.getDescriptionId()).isEqualTo(ViewsExplorerTreeDescriptionProvider.DESCRIPTION_ID);
65+
assertThat(tree.getChildren()).hasSize(1);
66+
assertThat(tree.getChildren()).allSatisfy(treeItem -> assertThat(treeItem.getChildren()).hasSize(1));
67+
assertThat(tree.getChildren().get(0).getChildren()).allSatisfy(treeItem -> assertThat(treeItem.getLabel().toString()).isEqualTo("REQUIREMENTS TABLE VIEW (1)"));
68+
});
69+
70+
StepVerifier.create(defaultFlux)
71+
.consumeNextWith(initialDefaultViewsContentConsumer)
72+
.thenCancel()
73+
.verify(Duration.ofSeconds(10));
74+
}
75+
76+
@DisplayName("GIVEN a project with an interconnection view, WHEN we subscribe to views events, THEN then the tree contains the interconnection view representation leaf")
77+
@GivenSysONServer({ InterconnectionViewEmptyTestProjectData.SCRIPT_PATH })
78+
@Test
79+
public void viewsWithInterconnectionViewRepresentation() {
80+
var representationId = new RepresentationIdBuilder().buildViewsExplorerViewRepresentationId(
81+
List.of(Diagram.KIND));
82+
var defaultExplorerInput = new ViewsExplorerEventInput(UUID.randomUUID(), InterconnectionViewEmptyTestProjectData.EDITING_CONTEXT_ID, representationId);
83+
var defaultFlux = this.viewsExplorerEventSubscriptionRunner.run(defaultExplorerInput).flux();
84+
85+
Consumer<Object> initialDefaultViewsContentConsumer = assertRefreshedTreeThat(tree -> {
86+
assertThat(tree).isNotNull();
87+
assertThat(tree.getDescriptionId()).isEqualTo(ViewsExplorerTreeDescriptionProvider.DESCRIPTION_ID);
88+
assertThat(tree.getChildren()).hasSize(1);
89+
assertThat(tree.getChildren()).allSatisfy(treeItem -> assertThat(treeItem.getChildren()).hasSize(1));
90+
assertThat(tree.getChildren().get(0).getChildren()).allSatisfy(treeItem -> assertThat(treeItem.getLabel().toString()).isEqualTo("INTERCONNECTION VIEW (1)"));
91+
});
92+
93+
StepVerifier.create(defaultFlux)
94+
.consumeNextWith(initialDefaultViewsContentConsumer)
95+
.thenCancel()
96+
.verify(Duration.ofSeconds(10));
97+
}
98+
}

backend/services/syson-services/src/main/java/org/eclipse/syson/util/StandardDiagramsConstants.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2025 Obeo.
2+
* Copyright (c) 2025, 2026 Obeo.
33
* This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v2.0
55
* which accompanies this distribution, and is available at
@@ -12,6 +12,9 @@
1212
*******************************************************************************/
1313
package org.eclipse.syson.util;
1414

15+
import java.util.HashMap;
16+
import java.util.Map;
17+
1518
/**
1619
* StandardDiagrams-related constants.
1720
*
@@ -35,4 +38,17 @@ public class StandardDiagramsConstants {
3538

3639
public static final String STV_QN = "StandardViewDefinitions::StateTransitionView";
3740

41+
public static final Map<String, String> SHORT_NAME_TO_VALUE = new HashMap<>();
42+
43+
static {
44+
SHORT_NAME_TO_VALUE.put("gv", GV);
45+
SHORT_NAME_TO_VALUE.put("iv", IV);
46+
SHORT_NAME_TO_VALUE.put("afv", AFV);
47+
SHORT_NAME_TO_VALUE.put("stv", STV);
48+
}
49+
50+
public static String getValueFromShortName(String shortName) {
51+
return SHORT_NAME_TO_VALUE.get(shortName.toLowerCase());
52+
}
53+
3854
}
12.1 KB
Loading

doc/content/modules/user-manual/pages/release-notes/2026.3.0.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,9 @@ Users can navigate to `/libraries/<library identifier>` to open a library and se
208208
** Add support for excluding libraries in the _Search_ view.
209209
The _Search in libraries_ toggle in the _Search_ view allows to include elements from user and standard libraries in the search.
210210
This toggle is de-activated by default.
211+
** In the _Views Explorer_ view, diagrams are now grouped by _view definition_.
211212

213+
image::release-note-views-explorer.png[_Views Explorer_ view with diagrams and table, width=60%,height=60%]
212214

213215
== Technical details
214216

0 commit comments

Comments
 (0)