Skip to content

Commit 0d62629

Browse files
pcdavidAxelRICHARD
authored andcommitted
[2116] Add a filter in the Explorer to hide the internal of Expression
Bug: #2116 Signed-off-by: Pierre-Charles David <pierre-charles.david@obeo.fr>
1 parent abd6d61 commit 0d62629

17 files changed

Lines changed: 402 additions & 14 deletions

File tree

CHANGELOG.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
=== New features
2020

2121
- https://github.com/eclipse-syson/syson/issues/2174[#2174] [diagrams] Add a new edge tool creating a _frame_ graphical edge between a `RequirementUsage` or a `RequirementDefinition` graphical node, and a `ConcernUsage` graphical node.
22+
- https://github.com/eclipse-syson/syson/issues/2116[#2116] [explorer] In the _Explorer_ view, the items corresponding to the internals of `Expression` elements (syntax tree) are now hidden by default.
23+
Disabling the _Hide expression internals_ filter in the _Explorer_ view allows to display them if needed.
2224

2325
== v2026.5.0
2426

backend/application/syson-application/src/test/java/org/eclipse/syson/application/controller/explorer/view/SysONExplorerTests.java

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.eclipse.syson.application.controller.explorer.testers.TreeItemContextMenuTester;
4848
import org.eclipse.syson.application.controller.explorer.testers.TreePathTester;
4949
import org.eclipse.syson.application.data.ActionTransitionUsagesProjectData;
50+
import org.eclipse.syson.application.data.ExpressionSamplesProjectData;
5051
import org.eclipse.syson.application.data.GeneralViewEmptyTestProjectData;
5152
import org.eclipse.syson.application.data.ProjectWithLibraryDependencyContainingCommentAndLibraryPackageTestProjectData;
5253
import org.eclipse.syson.application.data.ProjectWithLibraryDependencyContainingLibraryPackageTestProjectData;
@@ -871,4 +872,128 @@ public void sysONExplorerTreeExpressionLabelTest() {
871872
.thenCancel()
872873
.verify(Duration.ofSeconds(10));
873874
}
875+
876+
@DisplayName("GIVEN the SysON Explorer, WHEN displaying an Expression item, THEN its internal details are hidden by default")
877+
@GivenSysONServer({ ExpressionSamplesProjectData.SCRIPT_PATH })
878+
@Test
879+
public void sysONExplorerHidesExpressionInternalsByDefault() {
880+
List<String> defaultFilters = this.explorerDefaultFiltersSearchService.findTreeDefaultFilterIds(ExpressionSamplesProjectData.EDITING_CONTEXT_ID, this.sysONExplorerTreeDescriptionId);
881+
882+
var expandedItemIds = List.of(
883+
ExpressionSamplesProjectData.SemanticIds.EXPRESSIONS_DOCUMENT_ID,
884+
ExpressionSamplesProjectData.SemanticIds.EXPRESSIONS_PACKAGE_ID,
885+
ExpressionSamplesProjectData.SemanticIds.TANK_ID,
886+
ExpressionSamplesProjectData.SemanticIds.TANK_MAX_VOLUME_ATTRIBUTE_ID,
887+
ExpressionSamplesProjectData.SemanticIds.TANK_MAX_VOLUME_ATTRIBUTE_VALUE_ID,
888+
ExpressionSamplesProjectData.SemanticIds.TANK_PRESSURE_LIMIT_CONSTRAINT_ID,
889+
ExpressionSamplesProjectData.SemanticIds.TANK_PRESSURE_LIMIT_CONSTRAINT_VALUE_ID);
890+
891+
var explorerRepresentationId = this.representationIdBuilder.buildExplorerRepresentationId(this.sysONExplorerTreeDescriptionId, expandedItemIds, defaultFilters);
892+
var input = new ExplorerEventInput(UUID.randomUUID(), ExpressionSamplesProjectData.EDITING_CONTEXT_ID, explorerRepresentationId);
893+
var flux = this.explorerEventSubscriptionRunner.run(input).flux();
894+
TestTransaction.flagForCommit();
895+
TestTransaction.end();
896+
897+
var treeId = new AtomicReference<String>();
898+
Consumer<Object> initialTreeContentConsumer = assertRefreshedTreeThat(tree -> {
899+
assertThat(tree).isNotNull();
900+
treeId.set(tree.getId());
901+
assertThat(tree.getChildren()).hasSize(2);
902+
var documentItem = tree.getChildren().get(0);
903+
assertThat(documentItem.getChildren()).hasSize(1);
904+
assertThat(documentItem.getLabel().toString()).isEqualTo("expressions.sysml");
905+
var packageItem = documentItem.getChildren().get(0);
906+
assertThat(packageItem.getLabel().toString()).isEqualTo("Expressions");
907+
908+
assertThat(packageItem.getChildren()).hasSize(7);
909+
910+
var tankItem = this.getChildByLabel(packageItem, "Tank");
911+
assertThat(tankItem).isPresent();
912+
913+
var maxVolumeAttribute = this.getChildByLabel(tankItem.get(), "maxVolume");
914+
assertThat(maxVolumeAttribute).isPresent();
915+
assertThat(maxVolumeAttribute.get().getChildren()).hasSize(2);
916+
var maxVolumeAttributeValueItem = maxVolumeAttribute.get().getChildren().get(0);
917+
assertThat(maxVolumeAttributeValueItem.getLabel().toString()).isEqualTo("100.0 * minVolume");
918+
assertThat(maxVolumeAttributeValueItem.getChildren()).isEmpty(); // Details filtered out by default
919+
920+
var pressureLimitAttribute = this.getChildByLabel(tankItem.get(), "pressureLimit");
921+
assertThat(pressureLimitAttribute).isPresent();
922+
assertThat(pressureLimitAttribute.get().getChildren()).hasSize(1);
923+
var pressureLimitAttributeValueItem = pressureLimitAttribute.get().getChildren().get(0);
924+
assertThat(pressureLimitAttributeValueItem.getLabel().toString()).isEqualTo("pressure <= maxPressure");
925+
assertThat(pressureLimitAttributeValueItem.getChildren()).isEmpty(); // Details filtered out by default
926+
927+
});
928+
929+
StepVerifier.create(flux)
930+
.consumeNextWith(initialTreeContentConsumer)
931+
.thenCancel()
932+
.verify(Duration.ofSeconds(10));
933+
}
934+
935+
@DisplayName("GIVEN the SysON Explorer, WHEN displaying an Expression item, THEN its internal details can be revealed by disabling the corresponding filter")
936+
@GivenSysONServer({ ExpressionSamplesProjectData.SCRIPT_PATH })
937+
@Test
938+
public void sysONExplorerExpressionInternalsCanBeRevealed() {
939+
List<String> defaultFilters = this.explorerDefaultFiltersSearchService.findTreeDefaultFilterIds(ExpressionSamplesProjectData.EDITING_CONTEXT_ID, this.sysONExplorerTreeDescriptionId);
940+
// Keep all defaults but HIDE_EXPRESSION_INTERNALS_ID
941+
List<String> activeFilters = defaultFilters.stream().filter(filterId -> !SysONTreeFilterConstants.HIDE_EXPRESSION_INTERNALS_ID.equals(filterId)).toList();
942+
943+
var expandedItemIds = List.of(
944+
ExpressionSamplesProjectData.SemanticIds.EXPRESSIONS_DOCUMENT_ID,
945+
ExpressionSamplesProjectData.SemanticIds.EXPRESSIONS_PACKAGE_ID,
946+
ExpressionSamplesProjectData.SemanticIds.TANK_ID,
947+
ExpressionSamplesProjectData.SemanticIds.TANK_MAX_VOLUME_ATTRIBUTE_ID,
948+
ExpressionSamplesProjectData.SemanticIds.TANK_MAX_VOLUME_ATTRIBUTE_VALUE_ID,
949+
ExpressionSamplesProjectData.SemanticIds.TANK_PRESSURE_LIMIT_CONSTRAINT_ID,
950+
ExpressionSamplesProjectData.SemanticIds.TANK_PRESSURE_LIMIT_CONSTRAINT_VALUE_ID);
951+
952+
var explorerRepresentationId = this.representationIdBuilder.buildExplorerRepresentationId(this.sysONExplorerTreeDescriptionId, expandedItemIds, activeFilters);
953+
var input = new ExplorerEventInput(UUID.randomUUID(), ExpressionSamplesProjectData.EDITING_CONTEXT_ID, explorerRepresentationId);
954+
var flux = this.explorerEventSubscriptionRunner.run(input).flux();
955+
TestTransaction.flagForCommit();
956+
TestTransaction.end();
957+
958+
var treeId = new AtomicReference<String>();
959+
Consumer<Object> initialTreeContentConsumer = assertRefreshedTreeThat(tree -> {
960+
assertThat(tree).isNotNull();
961+
treeId.set(tree.getId());
962+
assertThat(tree.getChildren()).hasSize(2);
963+
var documentItem = tree.getChildren().get(0);
964+
assertThat(documentItem.getChildren()).hasSize(1);
965+
assertThat(documentItem.getLabel().toString()).isEqualTo("expressions.sysml");
966+
var packageItem = documentItem.getChildren().get(0);
967+
assertThat(packageItem.getLabel().toString()).isEqualTo("Expressions");
968+
969+
assertThat(packageItem.getChildren()).hasSize(7);
970+
971+
var tankItem = this.getChildByLabel(packageItem, "Tank");
972+
assertThat(tankItem).isPresent();
973+
974+
var maxVolumeAttribute = this.getChildByLabel(tankItem.get(), "maxVolume");
975+
assertThat(maxVolumeAttribute).isPresent();
976+
assertThat(maxVolumeAttribute.get().getChildren()).hasSize(2);
977+
var maxVolumeAttributeValueItem = maxVolumeAttribute.get().getChildren().get(0);
978+
assertThat(maxVolumeAttributeValueItem.getLabel().toString()).isEqualTo("100.0 * minVolume");
979+
assertThat(maxVolumeAttributeValueItem.getChildren()).hasSize(2); // Details visible
980+
981+
var pressureLimitAttribute = this.getChildByLabel(tankItem.get(), "pressureLimit");
982+
assertThat(pressureLimitAttribute).isPresent();
983+
assertThat(pressureLimitAttribute.get().getChildren()).hasSize(1);
984+
var pressureLimitAttributeValueItem = pressureLimitAttribute.get().getChildren().get(0);
985+
assertThat(pressureLimitAttributeValueItem.getLabel().toString()).isEqualTo("pressure <= maxPressure");
986+
assertThat(pressureLimitAttributeValueItem.getChildren()).hasSize(2); // Details visible
987+
988+
});
989+
990+
StepVerifier.create(flux)
991+
.consumeNextWith(initialTreeContentConsumer)
992+
.thenCancel()
993+
.verify(Duration.ofSeconds(10));
994+
}
995+
996+
private Optional<TreeItem> getChildByLabel(TreeItem parent, String label) {
997+
return parent.getChildren().stream().filter(child -> child.getLabel().toString().equals(label)).findFirst();
998+
}
874999
}

backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/explorer/view/ExplorerViewControllerIntegrationTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,8 @@ public void testSysONFiltersOnSysONExplorer() {
399399
SysONTreeFilterConstants.HIDE_SYSML_STANDARD_LIBRARIES_TREE_FILTER_ID,
400400
SysONTreeFilterConstants.HIDE_USER_LIBRARIES_TREE_FILTER_ID,
401401
SysONTreeFilterConstants.HIDE_ROOT_NAMESPACES_ID,
402-
SysONTreeFilterConstants.HIDE_EXPOSE_ELEMENTS_TREE_ITEM_FILTER_ID);
402+
SysONTreeFilterConstants.HIDE_EXPOSE_ELEMENTS_TREE_ITEM_FILTER_ID,
403+
SysONTreeFilterConstants.HIDE_EXPRESSION_INTERNALS_ID);
403404
}
404405

405406
@DisplayName("GIVEN the Sirius Explorer View, WHEN querying the filters, THEN no syson filters are returned")
Lines changed: 44 additions & 0 deletions
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+
* Ids for project "Expression-Samples".
17+
*
18+
* @author pcdavid
19+
*/
20+
public class ExpressionSamplesProjectData {
21+
public static final String EDITING_CONTEXT_ID = "aac82b89-8d53-4b16-91b4-deccc180ba62";
22+
23+
public static final String SCRIPT_PATH = "/scripts/database-content/ExpressionSamples.sql";
24+
25+
/**
26+
* Ids of the semantic elements.
27+
*/
28+
public static final class SemanticIds {
29+
public static final String EXPRESSIONS_DOCUMENT_ID = "ef3e6929-0415-4295-b42f-64bed8bd4f55";
30+
31+
public static final String EXPRESSIONS_PACKAGE_ID = "baf5ea6a-7861-4b92-8be2-3fe7a2ebc415";
32+
33+
public static final String TANK_ID = "2bde22f7-a834-4afa-84bf-1bae1bb434d8";
34+
35+
public static final String TANK_MAX_VOLUME_ATTRIBUTE_ID = "d9926727-7378-4177-a940-2fb6c1c89dce";
36+
37+
public static final String TANK_MAX_VOLUME_ATTRIBUTE_VALUE_ID = "a9216e54-f44c-4b1f-b262-13650324d325";
38+
39+
public static final String TANK_PRESSURE_LIMIT_CONSTRAINT_ID = "7d42ee06-3c27-4eaa-9438-344fc789906a";
40+
41+
public static final String TANK_PRESSURE_LIMIT_CONSTRAINT_VALUE_ID = "faa3b115-5b07-4ecf-9147-0d58ceffaf9c";
42+
43+
}
44+
}

backend/application/syson-application/src/test/resources/scripts/database-content/ExpressionSamples.sql

Lines changed: 98 additions & 0 deletions
Large diffs are not rendered by default.

backend/services/syson-sysml-metamodel-services/src/main/java/org/eclipse/syson/sysml/metamodel/services/MetamodelQueryElementService.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.function.Predicate;
2020

2121
import org.eclipse.syson.sysml.ActorMembership;
22+
import org.eclipse.syson.sysml.BooleanExpression;
2223
import org.eclipse.syson.sysml.ConcernUsage;
2324
import org.eclipse.syson.sysml.Connector;
2425
import org.eclipse.syson.sysml.Element;
@@ -82,16 +83,41 @@ public boolean isStakeholder(Element element) {
8283
}
8384

8485
/**
85-
* Check if a given {@code element} is an {@link Expression} definition. We can not simply rely on whether the
86-
* element is an instance of {@link Expression} as many types in SysMLv2 inherit from this type without being
87-
* themselves an actual expressions.
86+
* Check if a given {@link Element element} is an {@link Expression expression} definition. We can not simply rely
87+
* on whether the element is an instance of {@link Expression expression} as many types in SysMLv2 inherit from this
88+
* type without being themselves an actual expressions.
8889
*
8990
* @param element
9091
* the element to test.
9192
* @return true if the element is an actual expression definition.
9293
*/
9394
public boolean isExpressionDefinition(Element element) {
94-
return element instanceof Expression && !(element instanceof Usage);
95+
return element instanceof Expression && !(element instanceof Usage) && !(element instanceof BooleanExpression);
96+
}
97+
98+
/**
99+
* Check is a given {@link Element element} is a top-level {@link Expression expression}. In most cases, end-users
100+
* are only interested with these and not the internal elements which are technically also expressions but represent
101+
* parts of the overall expression.
102+
*
103+
* @param element
104+
* the element to test.
105+
* @return true if the element is a top-level expression definition.
106+
*/
107+
public boolean isTopLevelExpression(Element element) {
108+
boolean result = false;
109+
if (this.isExpressionDefinition(element)) {
110+
result = true;
111+
var ancestor = element.getOwner();
112+
while (ancestor != null) {
113+
if (this.isExpressionDefinition(ancestor)) {
114+
result = false;
115+
break;
116+
}
117+
ancestor = ancestor.getOwner();
118+
}
119+
}
120+
return result;
95121
}
96122

97123
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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.tree.explorer.filters;
14+
15+
import java.util.List;
16+
import java.util.Objects;
17+
18+
import org.eclipse.sirius.components.representations.VariableManager;
19+
import org.eclipse.sirius.web.application.views.explorer.services.api.IExplorerTreeItemAlteredContentProvider;
20+
import org.eclipse.syson.tree.explorer.services.api.ISysONExplorerFilterService;
21+
import org.springframework.stereotype.Service;
22+
23+
/**
24+
* An implementation of {@link IExplorerTreeItemAlteredContentProvider} allowing to hide internal elements of
25+
* expressions from Explorer tree.
26+
*
27+
* @author pcdavid
28+
*/
29+
@Service
30+
public class HideExpressionInternalsTreeItemAlteredContentProvider implements IExplorerTreeItemAlteredContentProvider {
31+
32+
private final ISysONExplorerFilterService filterService;
33+
34+
public HideExpressionInternalsTreeItemAlteredContentProvider(ISysONExplorerFilterService filterService) {
35+
this.filterService = Objects.requireNonNull(filterService);
36+
}
37+
38+
@Override
39+
public boolean canHandle(Object object, List<String> activeFilterIds) {
40+
return activeFilterIds.contains(SysONTreeFilterConstants.HIDE_EXPRESSION_INTERNALS_ID);
41+
}
42+
43+
@Override
44+
public List<Object> apply(List<Object> computedChildren, VariableManager variableManager) {
45+
return this.filterService.hideExpressionInternals(computedChildren);
46+
}
47+
}

backend/services/syson-tree-services/src/main/java/org/eclipse/syson/tree/explorer/filters/SysONTreeFilterConstants.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,6 @@ public class SysONTreeFilterConstants {
3232
public static final String HIDE_ROOT_NAMESPACES_ID = UUID.nameUUIDFromBytes("SysONTreeRootNamespacesFilter".getBytes()).toString();
3333

3434
public static final String HIDE_EXPOSE_ELEMENTS_TREE_ITEM_FILTER_ID = UUID.nameUUIDFromBytes("SysONTreeExposeElementsFilter".getBytes()).toString();
35+
36+
public static final String HIDE_EXPRESSION_INTERNALS_ID = UUID.nameUUIDFromBytes("SysONTreeExpressionInternalsFilter".getBytes()).toString();
3537
}

backend/services/syson-tree-services/src/main/java/org/eclipse/syson/tree/explorer/services/SysONExplorerFilterService.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@
2020
import org.eclipse.sirius.components.core.api.IEditingContext;
2121
import org.eclipse.syson.services.UtilService;
2222
import org.eclipse.syson.services.api.ISysONResourceService;
23+
import org.eclipse.syson.sysml.Element;
2324
import org.eclipse.syson.sysml.Expose;
2425
import org.eclipse.syson.sysml.LibraryPackage;
2526
import org.eclipse.syson.sysml.Membership;
2627
import org.eclipse.syson.sysml.Namespace;
28+
import org.eclipse.syson.sysml.metamodel.services.MetamodelQueryElementService;
2729
import org.eclipse.syson.sysml.util.ElementUtil;
2830
import org.eclipse.syson.tree.explorer.filters.SysONTreeFilterConstants;
2931
import org.eclipse.syson.tree.explorer.services.api.ISysONExplorerFilterService;
@@ -41,8 +43,11 @@ public class SysONExplorerFilterService implements ISysONExplorerFilterService {
4143

4244
private final ISysONResourceService sysONResourceService;
4345

46+
private final MetamodelQueryElementService metamodelQueryElementService;
47+
4448
public SysONExplorerFilterService(final ISysONResourceService sysONResourceService) {
4549
this.sysONResourceService = Objects.requireNonNull(sysONResourceService);
50+
this.metamodelQueryElementService = new MetamodelQueryElementService();
4651
}
4752

4853
@Override
@@ -127,6 +132,17 @@ public List<Object> hideExposeElements(List<Object> elements) {
127132
.toList();
128133
}
129134

135+
@Override
136+
public List<Object> hideExpressionInternals(List<Object> elements) {
137+
return elements.stream().filter(e -> {
138+
boolean isExpressionInternalElement = false;
139+
if (e instanceof Element element && !this.metamodelQueryElementService.isTopLevelExpression(element)) {
140+
isExpressionInternalElement = element.getOwner() != null && this.metamodelQueryElementService.isTopLevelExpression(element.getOwner());
141+
}
142+
return !isExpressionInternalElement;
143+
}).toList();
144+
}
145+
130146
@Override
131147
public List<Object> applyFilters(IEditingContext editingContext, List<?> elements, List<String> activeFilterIds) {
132148
List<Object> alteredElements = new ArrayList<>(elements);
@@ -148,6 +164,9 @@ public List<Object> applyFilters(IEditingContext editingContext, List<?> element
148164
if (activeFilterIds.contains(SysONTreeFilterConstants.HIDE_EXPOSE_ELEMENTS_TREE_ITEM_FILTER_ID)) {
149165
alteredElements = this.hideExposeElements(alteredElements);
150166
}
167+
if (activeFilterIds.contains(SysONTreeFilterConstants.HIDE_EXPRESSION_INTERNALS_ID)) {
168+
alteredElements = this.hideExpressionInternals(alteredElements);
169+
}
151170
return alteredElements;
152171
}
153172

backend/services/syson-tree-services/src/main/java/org/eclipse/syson/tree/explorer/services/api/ISysONExplorerFilterService.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2024, 2025 Obeo.
2+
* Copyright (c) 2024, 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
@@ -41,6 +41,8 @@ public interface ISysONExplorerFilterService {
4141

4242
List<Object> hideExposeElements(List<Object> elements);
4343

44+
List<Object> hideExpressionInternals(List<Object> elements);
45+
4446
List<Object> applyFilters(IEditingContext editingContext, List<?> elements, List<String> activeFilterIds);
4547

4648
}

0 commit comments

Comments
 (0)