Skip to content

Commit c548da1

Browse files
pcdavidAxelRICHARD
authored andcommitted
[2251] Allow expression-related operations on their parent element
Bug: #2251 Signed-off-by: Pierre-Charles David <pierre-charles.david@obeo.fr>
1 parent d256cc4 commit c548da1

14 files changed

Lines changed: 168 additions & 31 deletions

File tree

CHANGELOG.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
Before, the selection dialog option with selection allowed choosing between all _timeslice/snapshot_ types.
3232
Now, the choice is restricted to the _timeslice/snapshot_ type that match the graphical node type on which the tool is applied.
3333
- https://github.com/eclipse-syson/syson/issues/2119[#2119] [details] Display expressions values in the _Details_ view and allow to edit them from there.
34+
- https://github.com/eclipse-syson/syson/issues/2251[#2251] [explorer] Allow expression-related operations on their parent element
3435

3536
=== New features
3637

backend/application/syson-application-configuration/src/main/java/org/eclipse/syson/application/configuration/SysMLv2PropertiesConfigurer.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ private FormDescription createDetailsViewForElement() {
192192
pageCore.getGroups().add(this.createExtraTransitionSourceTargetPropertiesGroup());
193193
pageCore.getGroups().add(this.createFeatureValuePropertiesGroup());
194194
pageCore.getGroups().add(this.createExpressionPropertiesGroup());
195+
pageCore.getGroups().add(this.createResultExpressionPropertiesGroup());
195196

196197
PageDescription pageAdvanced = FormFactory.eINSTANCE.createPageDescription();
197198
pageAdvanced.setName("SysON-DetailsView-Advanced");
@@ -243,7 +244,7 @@ private GroupDescription createFeatureValuePropertiesGroup() {
243244

244245
TextAreaDescription expressionWidget = FormFactory.eINSTANCE.createTextAreaDescription();
245246
expressionWidget.setName("ValueExpression");
246-
expressionWidget.setLabelExpression("Value");
247+
expressionWidget.setLabelExpression(CUSTOM_EXPRESSION_WIDGET_KEY);
247248
expressionWidget.setValueExpression(ServiceMethod.of0(DetailsViewService::getValueExpressionTextualRepresentation).aqlSelf());
248249
expressionWidget.setIsEnabledExpression(AQLConstants.AQL_FALSE);
249250

@@ -252,6 +253,29 @@ private GroupDescription createFeatureValuePropertiesGroup() {
252253
return group;
253254
}
254255

256+
/**
257+
* Creates a group to display the value of a ResultExpression.
258+
*
259+
* @return a {@link GroupDescription}
260+
*/
261+
private GroupDescription createResultExpressionPropertiesGroup() {
262+
GroupDescription group = FormFactory.eINSTANCE.createGroupDescription();
263+
group.setDisplayMode(GroupDisplayMode.LIST);
264+
group.setName("Result");
265+
group.setLabelExpression("");
266+
group.setSemanticCandidatesExpression(ServiceMethod.of0(DetailsViewService::getResultExpression).aqlSelf());
267+
268+
TextAreaDescription expressionWidget = FormFactory.eINSTANCE.createTextAreaDescription();
269+
expressionWidget.setName("ResultExpression");
270+
expressionWidget.setLabelExpression(CUSTOM_EXPRESSION_WIDGET_KEY);
271+
expressionWidget.setValueExpression(ServiceMethod.of0(DetailsViewService::getResultExpressionTextualRepresentation).aqlSelf());
272+
expressionWidget.setIsEnabledExpression(AQLConstants.AQL_FALSE);
273+
274+
group.getChildren().add(expressionWidget);
275+
276+
return group;
277+
}
278+
255279
private GroupDescription createCorePropertiesGroup() {
256280
GroupDescription group = FormFactory.eINSTANCE.createGroupDescription();
257281
group.setDisplayMode(GroupDisplayMode.LIST);

backend/application/syson-application-configuration/src/main/java/org/eclipse/syson/application/expressions/services/ExpressionTextualRepresentationEventHandler.java

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.eclipse.sirius.components.core.api.IPayload;
2424
import org.eclipse.syson.application.expressions.dto.ExpressionTextualRepresentationInput;
2525
import org.eclipse.syson.application.expressions.dto.ExpressionTextualRepresentationPayload;
26+
import org.eclipse.syson.sysml.Element;
2627
import org.eclipse.syson.sysml.Expression;
2728
import org.eclipse.syson.sysml.metamodel.services.MetamodelQueryElementService;
2829
import org.springframework.stereotype.Service;
@@ -55,14 +56,38 @@ public boolean canHandle(IEditingContext editingContext, IInput input) {
5556
public void handle(Sinks.One<IPayload> payloadSink, Sinks.Many<ChangeDescription> changeDescriptionSink, IEditingContext editingContext, IInput input) {
5657
String textualRepresentation = "";
5758
if (input instanceof ExpressionTextualRepresentationInput expressionTextualRepresentationInput) {
58-
Optional<Expression> optionalExpression = this.objectSearchService.getObject(editingContext, expressionTextualRepresentationInput.elementId())
59-
.filter(Expression.class::isInstance)
60-
.map(Expression.class::cast)
61-
.filter(this.metamodelQueryElementService::isTopLevelExpression);
59+
String elementId = expressionTextualRepresentationInput.elementId();
60+
Optional<Expression> optionalExpression = this.getExpression(editingContext, elementId);
6261
if (optionalExpression.isPresent()) {
6362
textualRepresentation = this.metamodelQueryElementService.getExpressionTextualRepresentation(optionalExpression.get());
6463
}
6564
}
6665
payloadSink.tryEmitValue(new ExpressionTextualRepresentationPayload(input.id(), textualRepresentation));
6766
}
67+
68+
/**
69+
* Finds the {@link Expression} element to consider given the provided {@code elementId}.
70+
*
71+
* @param editingContext
72+
* the editing context.
73+
* @param elementId
74+
* either to id of an actual {@link Expression} element, or of the parent {@link Element} of a single
75+
* {@code Expression}.
76+
* @return the directly of indirectly designated {@link Expression}.
77+
*/
78+
private Optional<Expression> getExpression(IEditingContext editingContext, String elementId) {
79+
Optional<Expression> result = Optional.empty();
80+
Optional<Element> optionalElement = this.objectSearchService.getObject(editingContext, elementId)
81+
.filter(Element.class::isInstance)
82+
.map(Element.class::cast);
83+
if (optionalElement.isPresent()) {
84+
Element element = optionalElement.get();
85+
if (element instanceof Expression expression && this.metamodelQueryElementService.isTopLevelExpression(expression)) {
86+
result = optionalElement.map(Expression.class::cast);
87+
} else {
88+
result = this.metamodelQueryElementService.findSingleExpressionDefinition(element);
89+
}
90+
}
91+
return result;
92+
}
6893
}

backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/expressions/ExpressionsControllersIntegrationTests.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -553,18 +553,21 @@ public void topLevelExpressionTextualRepresentation() {
553553
treeId.set(tree.getId());
554554
});
555555

556-
// The Tank part and its attribute are not themselves expressions => ""
556+
// The Tank part is not an expression => ""
557557
var checkTank = this.checkExpressiontTextualRepresentation(editingContextId, ExpressionSamplesProjectData.SemanticIds.TANK_ID, "");
558-
var checkTankAttribute = this.checkExpressiontTextualRepresentation(editingContextId, ExpressionSamplesProjectData.SemanticIds.TANK_MAX_VOLUME_ATTRIBUTE_ID, "");
558+
// The Tank's attribute is not an expression itself either, but it owns a single, non-ambiguous expression to
559+
// can act as a "shortcut" to interact with id.
560+
var checkTankAttribute = this.checkExpressiontTextualRepresentation(editingContextId, ExpressionSamplesProjectData.SemanticIds.TANK_MAX_VOLUME_ATTRIBUTE_ID, "100.0 * minVolume");
559561
// The actual attribute default value expression however should be correctly represented
560562
var checkTankAttributeValueExpression = this.checkExpressiontTextualRepresentation(editingContextId, ExpressionSamplesProjectData.SemanticIds.TANK_MAX_VOLUME_ATTRIBUTE_VALUE_ID,
561563
"100.0 * minVolume");
562564

563565
var checkPerformanceConcern = this.checkExpressiontTextualRepresentation(editingContextId, ExpressionSamplesProjectData.SemanticIds.PERFORMANCE_CONCERN_ID, "");
564566
// A ConstaintUsage *is* an Expression from the point of view of SysML's type hierarchy, but not a top-level
565-
// Expression, so we expect "" here.
567+
// Expression. However if it contains a single expression, it an act as a "shortcut" to it.
566568
var checkPerformanceConcernAssumeConstraint = this.checkExpressiontTextualRepresentation(editingContextId, ExpressionSamplesProjectData.SemanticIds.PERFORMANCE_CONCERN_ASSUME_ID, "");
567-
var checkPerformanceConcernRequireConstraint = this.checkExpressiontTextualRepresentation(editingContextId, ExpressionSamplesProjectData.SemanticIds.PERFORMANCE_CONCERN_REQUIRE_ID, "");
569+
var checkPerformanceConcernRequireConstraint = this.checkExpressiontTextualRepresentation(editingContextId, ExpressionSamplesProjectData.SemanticIds.PERFORMANCE_CONCERN_REQUIRE_ID,
570+
"s.samplingRate >= 50.0 & s.currentValue != 0.0 | s.errorCount == 0");
568571
// require s.samplingRate >= 50.0 & s.currentValue != 0.0 | s.errorCount == 0
569572
var checkPerformanceConcernRequireConstraintExpression = this.checkExpressiontTextualRepresentation(editingContextId,
570573
ExpressionSamplesProjectData.SemanticIds.PERFORMANCE_CONCERN_REQUIRE_EXPRESSION_ID, "s.samplingRate >= 50.0 & s.currentValue != 0.0 | s.errorCount == 0");

backend/application/syson-sysml-import/src/main/java/org/eclipse/syson/sysml/dto/EditExpressionInput.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@
2121
*
2222
* @author pcdavid
2323
*/
24-
public record EditExpressionInput(UUID id, String editingContextId, String expressionElementId, String newExpressionText) implements IInput {
24+
public record EditExpressionInput(UUID id, String editingContextId, String elementId, String newExpressionText) implements IInput {
2525
}

backend/application/syson-sysml-import/src/main/java/org/eclipse/syson/sysml/services/EditExpressionEventHandler.java

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.eclipse.syson.sysml.Expression;
3232
import org.eclipse.syson.sysml.dto.EditExpressionInput;
3333
import org.eclipse.syson.sysml.dto.EditExpressionSuccessPayload;
34+
import org.eclipse.syson.sysml.metamodel.services.MetamodelQueryElementService;
3435
import org.eclipse.syson.sysml.services.api.ISysMLExpressionEditor;
3536
import org.springframework.stereotype.Service;
3637

@@ -54,6 +55,8 @@ public class EditExpressionEventHandler implements IEditingContextEventHandler {
5455

5556
private final ISysMLExpressionEditor expressionEditor;
5657

58+
private final MetamodelQueryElementService metamodelQueryElementService;
59+
5760
private final Counter counter;
5861

5962
public EditExpressionEventHandler(IObjectSearchService objectSearchService, IIdentityService identityService, ICollaborativeMessageService messageService, ISysMLExpressionEditor expressionEditor,
@@ -62,6 +65,7 @@ public EditExpressionEventHandler(IObjectSearchService objectSearchService, IIde
6265
this.identityService = Objects.requireNonNull(identityService);
6366
this.messageService = Objects.requireNonNull(messageService);
6467
this.expressionEditor = Objects.requireNonNull(expressionEditor);
68+
this.metamodelQueryElementService = new MetamodelQueryElementService();
6569
this.counter = Counter.builder(Monitoring.EVENT_HANDLER)
6670
.tag(Monitoring.NAME, this.getClass().getSimpleName())
6771
.register(meterRegistry);
@@ -79,13 +83,8 @@ public void handle(Sinks.One<IPayload> payloadSink, Sinks.Many<ChangeDescription
7983
ChangeDescription changeDescription = new ChangeDescription(ChangeKind.NOTHING, editingContext.getId(), input);
8084

8185
if (input instanceof EditExpressionInput editExpressionInput && editingContext instanceof IEMFEditingContext emfEditingContext) {
82-
Optional<Expression> optionalExpression = this.objectSearchService.getObject(editingContext, editExpressionInput.expressionElementId())
83-
.filter(Expression.class::isInstance)
84-
.map(Expression.class::cast);
85-
Optional<Element> optionalParent = Optional.empty();
86-
if (optionalExpression.isPresent()) {
87-
optionalParent = Optional.ofNullable(optionalExpression.get().getOwner());
88-
}
86+
var optionalParent = this.getExpressionParent(emfEditingContext, editExpressionInput.elementId());
87+
var optionalExpression = this.getExpression(emfEditingContext, editExpressionInput.elementId());
8988

9089
if (optionalParent.isPresent() && optionalExpression.isPresent()) {
9190
var result = this.expressionEditor.editExpression(emfEditingContext, optionalParent.get(), optionalExpression.get(), editExpressionInput.newExpressionText());
@@ -108,4 +107,57 @@ public void handle(Sinks.One<IPayload> payloadSink, Sinks.Many<ChangeDescription
108107
payloadSink.tryEmitValue(payload);
109108
changeDescriptionSink.tryEmitNext(changeDescription);
110109
}
110+
111+
/**
112+
* Finds the {@link Expression} element to consider given the provided {@code elementId}.
113+
*
114+
* @param editingContext
115+
* the editing context.
116+
* @param elementId
117+
* either to id of an actual {@link Expression} element, or of the parent {@link Element} of a single
118+
* {@code Expression}.
119+
* @return the directly of indirectly designated {@link Expression}.
120+
*/
121+
private Optional<Expression> getExpression(IEditingContext editingContext, String elementId) {
122+
Optional<Expression> result = Optional.empty();
123+
Optional<Element> optionalElement = this.objectSearchService.getObject(editingContext, elementId)
124+
.filter(Element.class::isInstance)
125+
.map(Element.class::cast);
126+
if (optionalElement.isPresent()) {
127+
Element element = optionalElement.get();
128+
if (element instanceof Expression expression && this.metamodelQueryElementService.isTopLevelExpression(expression)) {
129+
result = optionalElement.map(Expression.class::cast);
130+
} else {
131+
result = this.metamodelQueryElementService.findSingleExpressionDefinition(element);
132+
}
133+
}
134+
return result;
135+
}
136+
137+
/**
138+
* Finds the {@link Expression} element to consider given the provided {@code elementId}.
139+
*
140+
* @param editingContext
141+
* the editing context.
142+
* @param elementId
143+
* either to id of an actual {@link Expression} element, or of the parent {@link Element} of a single
144+
* {@code Expression}.
145+
* @return the directly of indirectly designated {@link Expression}.
146+
*/
147+
private Optional<Element> getExpressionParent(IEditingContext editingContext, String elementId) {
148+
Optional<Element> result = Optional.empty();
149+
150+
Optional<Element> optionalElement = this.objectSearchService.getObject(editingContext, elementId)
151+
.filter(Element.class::isInstance)
152+
.map(Element.class::cast);
153+
if (optionalElement.isPresent()) {
154+
Element element = optionalElement.get();
155+
if (element instanceof Expression expression && this.metamodelQueryElementService.isTopLevelExpression(expression)) {
156+
result = optionalElement.map(Element::getOwner);
157+
} else {
158+
result = optionalElement;
159+
}
160+
}
161+
return result;
162+
}
111163
}

backend/application/syson-sysml-import/src/main/resources/schema/syson-import.graphqls

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type CreateExpressionSuccessPayload {
3434
input EditExpressionInput {
3535
id: ID!
3636
editingContextId: ID!
37-
expressionElementId: ID!
37+
elementId: ID!
3838
newExpressionText: String!
3939
}
4040

backend/views/syson-tree-explorer-view/src/main/java/org/eclipse/syson/tree/explorer/view/menu/context/SysONExplorerTreeItemContextMenuEntryProvider.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,8 @@ private void addExpressionEditionEntries(List<ITreeItemContextMenuEntry> entries
192192
expressionEntries.add(EDIT_EXPRESSION_MENU_ENTRY_CONTRIBUTION_ID);
193193
} else if (this.metamodelQueryElementService.hasSingleExpressionDefinition(element)
194194
&& !this.metamodelQueryElementService.hasSingleExpressionDefinition(element.getOwner())) {
195-
// "Delete expression" on the owner of a root Expression element
195+
// "Edit expression" and "Delete expression" on the owner of a root Expression element
196+
expressionEntries.add(EDIT_EXPRESSION_MENU_ENTRY_CONTRIBUTION_ID);
196197
expressionEntries.add(DELETE_EXPRESSION_MENU_ENTRY_CONTRIBUTION_ID);
197198
}
198199

doc/content/modules/user-manual/pages/features/expressions.adoc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ a|image::explorer-expression-internals-hidden.png[Internals hidden (default)]
1919
a|image::explorer-expression-internals-visible.png[Internals visible]
2020
|===
2121

22-
When an `Expression` element is selected(for example from the {explorer} view), the {details}} view displays its textual representation, with a button to open the expression edition modal (see below) directly on it:
22+
When an `Expression` element or an `Element` which contains a single `Expression` element is selected (for example from the {explorer} view), the {details}} view displays the textual representation of the expression, with a button to open the expression edition modal (see below) directly on it:
2323
+
2424
image::expression-details.png[Expression value displayed in the _Details_ veiw, width=80%]
2525

@@ -39,10 +39,10 @@ image::edit-expression-modal-error.png[Edit expression modal showing an error me
3939

4040
== Edition
4141

42-
To edit an already existing expression, invoke the _Edit expression_ context menu action directly on the existing expression.
42+
To edit an already existing expression, invoke the _Edit expression_ context menu action directly on the existing expression or on its parent element.
4343
The same modal as for expression creation will open, but with the current textual representation of the expression pre-filled.
4444

45-
Edit the textual representation of the expression as required, and click on the _Update_ button to validate.
45+
Edit the textual representation of the expression as required, and click on the _Update_ button or hit _Ctrl-RET_ to validate.
4646

4747
As for expression creation, the change will only be applied if there are no errors detected; otherwise the modal will display any errors (e.g. names used in the expression that can not be resolve to existing elements) so that you can fix the input.
4848

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,21 +54,21 @@ In both cases, to display the `FramedConcernMembership`, deactivate the `Hide Me
5454
** It is now possible to create, edit and delete _Expressions_ using their textual syntax from the _Explorer_ view.
5555
To _create_ an expression, invoke the _New expression_ context menu action on a compatible element (supported elements are `Attributes`, `Constraints` and `Transitions`).
5656
A modal will open where you can enter the textual representation of the expression to create.
57-
Click on the _Update_ button to validate.
57+
Click on the _Update_ button or hit _Ctrl-RET_ to validate.
5858
If the entered expression is valid, it will be created and the modal will automatically close.
5959
If the entered expression is _not_ valid, the modal will remain open and show the error(s) in an expandable accordion.
6060
Fix any error reported before clicking on _Update_ again (or canceling the operation).
6161
+
6262
image::edit-expression-modal.png[Edit expression modal, width=80%]
6363
image::edit-expression-modal-error.png[Edit expression modal showing an error message if the new expression is invalid, width=80%]
6464
+
65-
To _edit_ an existing expression, simply invoke the _Edit expression_ context menu action directly on the existing expression; the same modal will open but with the current textual representation of the expression pre-filled.
65+
To _edit_ an existing expression, simply invoke the _Edit expression_ context menu action directly on the existing expression or on its parent element; the same modal will open but with the current textual representation of the expression pre-filled.
6666
+
6767
To _delete_ an existing expression, you can simply invoke the normal _Delete_ menu item action on the expression itself or the new _Delete expression_ on its parent element.
6868

6969
* In the _Details_ view:
7070

71-
** When an `Expression` element is selected (for example from the _Explorer_ view), the _Details_ view displays its textual representation, with a button to open the expression edition modal (see above) directly on it:
71+
** When an `Expression` element or an `Element` which contains a single `Expression` element is selected (for example from the _Explorer_ view), the _Details_ view displays the textual representation of the `Expression`, with a button to open the expression edition modal (see above) directly on it:
7272
+
7373
image::expression-details.png[Expression value displayed in the _Details_ veiw, width=80%]
7474

0 commit comments

Comments
 (0)