diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index b5e89001e..e87f0d53d 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -12,6 +12,7 @@ - [services] All services that were previously available in `org.eclipse.syson.sysml.textual` package of `syson-sysml-metamodel` module have been moved to `org.eclipse.syson.sysml.metamodel.services.textual` package of `syson-sysml-metamodel-services` module. - [services] All services that were previously available in `org.eclipse.syson.sysml.textual.utils` package of `syson-sysml-metamodel` module have been moved to `org.eclipse.syson.sysml.metamodel.services.textual.utils` package of `syson-sysml-metamodel-services` module. - [services] All services that were previously available in `org.eclipse.syson.sysml.util` package of `syson-sysml-metamodel` module have been moved to `org.eclipse.syson.sysml.metamodel.util` package of `syson-sysml-metamodel-services` module. +- [services] All methods from `org.eclipse.syson.util.AQLUtils` except `aqlString` and `aqlSequence` have been removed. - [frontend] The `variables.css` file, which used to define custom colors an typography settings has been removed. It has long been unused by Sirius Web itself (since the transition to MUI). @@ -39,8 +40,10 @@ It has long been unused by Sirius Web itself (since the transition to MUI). Before, the selection dialog option with selection allowed choosing between all _timeslice/snapshot_ types. Now, the choice is restricted to the _timeslice/snapshot_ type that match the graphical node type on which the tool is applied. - https://github.com/eclipse-syson/syson/issues/2119[#2119] [details] Display expressions values in the _Details_ view and allow to edit them from there. -- https://github.com/eclipse-syson/syson/issues/2251[#2251] [explorer] Allow expression-related operations on their parent element -- https://github.com/eclipse-syson/syson/issues/2270[#2270] [diagram] Add dedicated tools to diagram elements palette to create/edit/delete expressions +- https://github.com/eclipse-syson/syson/issues/2251[#2251] [explorer] Allow expression-related operations on their parent element. +- https://github.com/eclipse-syson/syson/issues/2270[#2270] [diagram] Add dedicated tools to diagram elements palette to create/edit/delete expressions. +- https://github.com/eclipse-syson/syson/issues/2269[#2269] [diagram] When using direct edit on feature elements which have a value expression (e.g. an `Attribute` with a default value defined), the expression part (after the `=` sign) is no longer part of the initial direct edit value, and will be ignored if supplied in the new text value. +Users should use the dedicated tools to edit expressions instead, as they ensure only valid expressions (with all names resolving) are accepted. === New features diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVDirectEditTests.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVDirectEditTests.java index a4044fb3c..558321a53 100644 --- a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVDirectEditTests.java +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVDirectEditTests.java @@ -22,6 +22,7 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; +import java.util.function.Supplier; import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramEventInput; import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramRefreshedEventPayload; @@ -35,6 +36,7 @@ import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState; import org.eclipse.syson.AbstractIntegrationTests; import org.eclipse.syson.GivenSysONServer; +import org.eclipse.syson.application.controllers.expressions.graphql.CreateExpressionMutationRunner; import org.eclipse.syson.application.data.GeneralViewDirectEditTestProjectData; import org.eclipse.syson.application.data.GeneralViewItemAndAttributeProjectData; import org.eclipse.syson.application.data.GeneralViewPartUsageRedefinitionProjectData; @@ -42,6 +44,8 @@ import org.eclipse.syson.services.diagrams.api.IGivenDiagramSubscription; import org.eclipse.syson.sysml.PartUsage; import org.eclipse.syson.sysml.PortionKind; +import org.eclipse.syson.sysml.dto.CreateExpressionInput; +import org.eclipse.syson.sysml.dto.CreateExpressionSuccessPayload; import org.eclipse.syson.sysml.metamodel.helper.LabelConstants; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -72,6 +76,9 @@ public class GVDirectEditTests extends AbstractIntegrationTests { @Autowired private EditLabelMutationRunner editLabelMutationRunner; + @Autowired + private CreateExpressionMutationRunner createExpressionMutationRunner; + @Autowired private IObjectSearchService objectSearchService; @@ -395,7 +402,7 @@ public void directEditUsingShortNameAndScope() { .verify(Duration.ofSeconds(10)); } - @DisplayName("GIVEN a diagram with an attribute, WHEN we direct edit with an operation using an unimported namespace, THEN the attribute is correctly set") + @DisplayName("GIVEN a diagram with an attribute, WHEN we set its value to an expression using an unimported namespace, THEN the attribute is correctly set") @GivenSysONServer({ GeneralViewItemAndAttributeProjectData.SCRIPT_PATH }) @Test public void directEditOperationUsingUnImportedNameSpaceName() { @@ -407,6 +414,7 @@ public void directEditOperationUsingUnImportedNameSpaceName() { var diagramId = new AtomicReference(); var partNodeId = new AtomicReference(); + var partNodeTargetId = new AtomicReference(); var partNodeLabelId = new AtomicReference(); Consumer initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram -> { @@ -414,26 +422,19 @@ public void directEditOperationUsingUnImportedNameSpaceName() { var partNode = new DiagramNavigator(diagram).nodeWithLabel("x1").getNode(); partNodeId.set(partNode.getId()); partNodeLabelId.set(partNode.getInsideLabel().getId()); + partNodeTargetId.set(partNode.getTargetObjectId()); }); - Runnable editLabel = () -> { - var input = new EditLabelInput(UUID.randomUUID(), GeneralViewItemAndAttributeProjectData.EDITING_CONTEXT_ID, diagramId.get(), partNodeLabelId.get(), "t1 = 1[g]"); - var result = this.editLabelMutationRunner.run(input); - - String typename = JsonPath.read(result.data(), "$.data.editLabel.__typename"); - assertThat(typename).isEqualTo(EditLabelSuccessPayload.class.getSimpleName()); - List messages = JsonPath.read(result.data(), "$.data.editLabel.messages[*].body"); - assertThat(messages).hasSize(0); - }; + Runnable createExpression = this.createExpression(GeneralViewItemAndAttributeProjectData.EDITING_CONTEXT_ID, partNodeTargetId::get, "1 [g]"); Consumer updatedDiagramContentMatcher = assertRefreshedDiagramThat(diagram -> { var node = new DiagramNavigator(diagram).nodeWithId(partNodeId.get()).getNode(); - DiagramAssertions.assertThat(node.getInsideLabel()).hasText("t1 = 1 [g]"); + DiagramAssertions.assertThat(node.getInsideLabel()).hasText("x1 = 1 [g]"); }); StepVerifier.create(flux) .consumeNextWith(initialDiagramContentConsumer) - .then(editLabel) + .then(createExpression) .consumeNextWith(updatedDiagramContentMatcher) .thenCancel() .verify(Duration.ofSeconds(10)); @@ -622,10 +623,10 @@ public void directEditMultiplicityWithFeatureTyping() { .verify(Duration.ofSeconds(10)); } - @DisplayName("GIVEN a diagram with a part, WHEN we direct edit with multiplicity and operation, THEN the part is correctly set only if the multiplicity is before the operation") + @DisplayName("GIVEN a diagram with a part, WHEN we direct edit with multiplicity, THEN the multiplicity is correctly set") @GivenSysONServer({ GeneralViewItemAndAttributeProjectData.SCRIPT_PATH }) @Test - public void directEditMultiplicityWithOperation() { + public void directEditMultiplicity() { var diagramEventInput = new DiagramEventInput(UUID.randomUUID(), GeneralViewItemAndAttributeProjectData.EDITING_CONTEXT_ID, GeneralViewItemAndAttributeProjectData.GraphicalIds.DIAGRAM_ID); @@ -644,7 +645,7 @@ public void directEditMultiplicityWithOperation() { }); Runnable editLabelWithMultiplicityBefore = () -> { - var input = new EditLabelInput(UUID.randomUUID(), GeneralViewItemAndAttributeProjectData.EDITING_CONTEXT_ID, diagramId.get(), partNodeLabelId.get(), "t1 [4] = 1[g]"); + var input = new EditLabelInput(UUID.randomUUID(), GeneralViewItemAndAttributeProjectData.EDITING_CONTEXT_ID, diagramId.get(), partNodeLabelId.get(), "t1 [4]"); var result = this.editLabelMutationRunner.run(input); String typename = JsonPath.read(result.data(), "$.data.editLabel.__typename"); @@ -653,20 +654,7 @@ public void directEditMultiplicityWithOperation() { assertThat(messages).hasSize(0); }; - Runnable editLabelWithMultiplicityAfter = () -> { - var input = new EditLabelInput(UUID.randomUUID(), GeneralViewItemAndAttributeProjectData.EDITING_CONTEXT_ID, diagramId.get(), partNodeLabelId.get(), "t1 = 1[g] [4]"); - var result = this.editLabelMutationRunner.run(input); - - String typename = JsonPath.read(result.data(), "$.data.editLabel.__typename"); - assertThat(typename).isEqualTo(EditLabelSuccessPayload.class.getSimpleName()); - }; - Consumer updatedDiagramContentMatcherBefore = assertRefreshedDiagramThat(diagram -> { - var node = new DiagramNavigator(diagram).nodeWithId(partNodeId.get()).getNode(); - DiagramAssertions.assertThat(node.getInsideLabel()).hasText("t1 [4] = 1 [g]"); - }); - - Consumer updatedDiagramContentMatcherAfter = assertRefreshedDiagramThat(diagram -> { var node = new DiagramNavigator(diagram).nodeWithId(partNodeId.get()).getNode(); DiagramAssertions.assertThat(node.getInsideLabel()).hasText("t1 [4]"); }); @@ -675,8 +663,6 @@ public void directEditMultiplicityWithOperation() { .consumeNextWith(initialDiagramContentConsumer) .then(editLabelWithMultiplicityBefore) .consumeNextWith(updatedDiagramContentMatcherBefore) - .then(editLabelWithMultiplicityAfter) - .consumeNextWith(updatedDiagramContentMatcherAfter) .thenCancel() .verify(Duration.ofSeconds(10)); } @@ -746,4 +732,13 @@ public void directEditRedefinitionWithSameName() { .thenCancel() .verify(Duration.ofSeconds(10)); } + + private Runnable createExpression(String editingContextId, Supplier parentElementId, String expressionContent) { + return () -> { + var input = new CreateExpressionInput(UUID.randomUUID(), editingContextId, parentElementId.get(), expressionContent); + var result = this.createExpressionMutationRunner.run(input); + String typename = JsonPath.read(result.data(), "$.data.createExpression.__typename"); + assertThat(typename).isEqualTo(CreateExpressionSuccessPayload.class.getSimpleName()); + }; + } } diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVItemAndAttributeExpressionTests.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVItemAndAttributeExpressionTests.java index 7a5bdb1d1..23923394b 100644 --- a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVItemAndAttributeExpressionTests.java +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVItemAndAttributeExpressionTests.java @@ -15,14 +15,18 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.sirius.components.diagrams.tests.DiagramEventPayloadConsumer.assertRefreshedDiagramThat; +import com.jayway.jsonpath.JsonPath; + import java.time.Duration; import java.util.List; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; +import java.util.function.Supplier; import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramEventInput; import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramRefreshedEventPayload; +import org.eclipse.sirius.components.core.api.SuccessPayload; import org.eclipse.sirius.components.diagrams.Diagram; import org.eclipse.sirius.components.diagrams.Edge; import org.eclipse.sirius.components.diagrams.InsideLabel; @@ -38,9 +42,17 @@ import org.eclipse.syson.application.controllers.diagrams.testers.DeleteToolTester; import org.eclipse.syson.application.controllers.diagrams.testers.DirectEditInitialLabelTester; import org.eclipse.syson.application.controllers.diagrams.testers.DirectEditTester; +import org.eclipse.syson.application.controllers.expressions.graphql.CreateExpressionMutationRunner; +import org.eclipse.syson.application.controllers.expressions.graphql.DeleteExpressionMutationRunner; +import org.eclipse.syson.application.controllers.expressions.graphql.EditExpressionMutationRunner; import org.eclipse.syson.application.data.GeneralViewItemAndAttributeProjectData; +import org.eclipse.syson.application.expressions.dto.DeleteExpressionInput; import org.eclipse.syson.services.diagrams.DiagramComparator; import org.eclipse.syson.services.diagrams.api.IGivenDiagramSubscription; +import org.eclipse.syson.sysml.dto.CreateExpressionInput; +import org.eclipse.syson.sysml.dto.CreateExpressionSuccessPayload; +import org.eclipse.syson.sysml.dto.EditExpressionInput; +import org.eclipse.syson.sysml.dto.EditExpressionSuccessPayload; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -57,6 +69,7 @@ * @author Arthur Daussy */ @Transactional +@SuppressWarnings("checkstyle:MultipleStringLiterals") @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class GVItemAndAttributeExpressionTests extends AbstractIntegrationTests { @@ -78,6 +91,15 @@ public class GVItemAndAttributeExpressionTests extends AbstractIntegrationTests @Autowired private DeleteToolRunner deleteFromDiagramRunner; + @Autowired + private CreateExpressionMutationRunner createExpressionMutationRunner; + + @Autowired + private EditExpressionMutationRunner editExpressionMutationRunner; + + @Autowired + private DeleteExpressionMutationRunner deleteExpressionMutationRunner; + private DirectEditInitialLabelTester directEditInitialLabelTester; private DirectEditTester directEditTester; @@ -132,8 +154,9 @@ public void nestedBinaryOperatorExpressionWithNameOverlapping() { Runnable directEditInitialLabelCheck = this.directEditInitialLabelTester.checkDirectEditInitialLabelOnNode(diagram, GeneralViewItemAndAttributeProjectData.GraphicalIds.P1_1_X1_ID, "x1"); - Runnable directEditInsideLabelCheck = this.directEditTester.directEditInsideLabel(diagram, GeneralViewItemAndAttributeProjectData.GraphicalIds.P1_1_X1_ID, "x1 = x2 + x3 + 4.5"); - Consumer directEditInsideLabelDiagramCheck = assertRefreshedDiagramThat(newDiagram -> { + Runnable createAttributeValueExpression = this.createExpression(GeneralViewItemAndAttributeProjectData.EDITING_CONTEXT_ID, GeneralViewItemAndAttributeProjectData.SemanticIds.P1_1_X1_ID, + "x2 + x3 + 4.5"); + Consumer createAttributeValueExpressionDiagramCheck = assertRefreshedDiagramThat(newDiagram -> { DiagramNavigator diagramNavigator = new DiagramNavigator(newDiagram); InsideLabel newLabel = diagramNavigator.nodeWithId(GeneralViewItemAndAttributeProjectData.GraphicalIds.P1_1_X1_ID).getNode().getInsideLabel(); @@ -142,11 +165,12 @@ public void nestedBinaryOperatorExpressionWithNameOverlapping() { // Using qualified name but keep pointing to attributes in P1_1 Runnable directEditInitialLabelCheck2 = this.directEditInitialLabelTester.checkDirectEditInitialLabelOnNode(diagram, GeneralViewItemAndAttributeProjectData.GraphicalIds.P1_1_X1_ID, - "x1 = x2 + x3 + 4.5"); + "x1"); - Runnable directEditInsideLabelDiagramCheck2 = this.directEditTester.directEditInsideLabel(diagram, GeneralViewItemAndAttributeProjectData.GraphicalIds.P1_1_X1_ID, - "x1 = RootPackage::p1::p1_1::x3 - RootPackage::p1::p1_1::x2 - 4.5"); // We use simple name for label - Consumer directEditInsideLabelDiagramCheck3 = assertRefreshedDiagramThat(newDiagram -> { + Runnable editExpression = this.editExpression(GeneralViewItemAndAttributeProjectData.EDITING_CONTEXT_ID, + () -> GeneralViewItemAndAttributeProjectData.SemanticIds.P1_1_X1_ID, + "RootPackage::p1::p1_1::x3 - RootPackage::p1::p1_1::x2 - 4.5"); // We use simple name for label + Consumer editExpressionLabelDiagramCheck = assertRefreshedDiagramThat(newDiagram -> { DiagramNavigator diagramNavigator = new DiagramNavigator(newDiagram); InsideLabel newLabel = diagramNavigator.nodeWithId(GeneralViewItemAndAttributeProjectData.GraphicalIds.P1_1_X1_ID).getNode().getInsideLabel(); @@ -155,11 +179,11 @@ public void nestedBinaryOperatorExpressionWithNameOverlapping() { // Now change the target to attributes defined in P1 Runnable directEditInitialLabelCheck3 = this.directEditInitialLabelTester.checkDirectEditInitialLabelOnNode(diagram, GeneralViewItemAndAttributeProjectData.GraphicalIds.P1_1_X1_ID, - "x1 = x3 - x2 - 4.5"); + "x1"); - Runnable directEditInsideLabelCheck4 = this.directEditTester.directEditInsideLabel(diagram, GeneralViewItemAndAttributeProjectData.GraphicalIds.P1_1_X1_ID, - "x1 = RootPackage::p1::x3 / RootPackage::p1::x3 * 10"); - Consumer directEditInsideLabelDiagramCheck4 = assertRefreshedDiagramThat(newDiagram -> { + Runnable editExpression2 = this.editExpression(GeneralViewItemAndAttributeProjectData.EDITING_CONTEXT_ID, () -> GeneralViewItemAndAttributeProjectData.SemanticIds.P1_1_X1_ID, + "RootPackage::p1::x3 / RootPackage::p1::x3 * 10"); + Consumer editExpressionLabelDiagramCheck2 = assertRefreshedDiagramThat(newDiagram -> { DiagramNavigator diagramNavigator = new DiagramNavigator(newDiagram); InsideLabel newLabel = diagramNavigator.nodeWithId(GeneralViewItemAndAttributeProjectData.GraphicalIds.P1_1_X1_ID).getNode().getInsideLabel(); @@ -169,19 +193,19 @@ public void nestedBinaryOperatorExpressionWithNameOverlapping() { // Here the direct edit input needs to explicitly give the qualified name of x3 since the default x3 is the one // located in p1_1 whereas we are targeting RootPackage::p1::x3 Runnable directEditInitialLabelCheck5 = this.directEditInitialLabelTester.checkDirectEditInitialLabelOnNode(diagram, GeneralViewItemAndAttributeProjectData.GraphicalIds.P1_1_X1_ID, - "x1 = RootPackage::p1::x3 / RootPackage::p1::x3 * 10"); + "x1"); StepVerifier.create(flux) .consumeNextWith(initialDiagramContentConsumer) .then(directEditInitialLabelCheck) - .then(directEditInsideLabelCheck) - .consumeNextWith(directEditInsideLabelDiagramCheck) + .then(createAttributeValueExpression) + .consumeNextWith(createAttributeValueExpressionDiagramCheck) .then(directEditInitialLabelCheck2) - .then(directEditInsideLabelDiagramCheck2) - .consumeNextWith(directEditInsideLabelDiagramCheck3) + .then(editExpression) + .consumeNextWith(editExpressionLabelDiagramCheck) .then(directEditInitialLabelCheck3) - .then(directEditInsideLabelCheck4) - .consumeNextWith(directEditInsideLabelDiagramCheck4) + .then(editExpression2) + .consumeNextWith(editExpressionLabelDiagramCheck2) .then(directEditInitialLabelCheck5) .thenCancel() .verify(Duration.ofSeconds(10)); @@ -209,18 +233,20 @@ public void payloadFeatureChain() { OutsideLabel newLabel = diagramNavigator.nodeWithId(GeneralViewItemAndAttributeProjectData.GraphicalIds.A2_1_BORDERED_NODE_ID).getNode().getOutsideLabels().get(0); // Bordered node do not display the feature value in their label assertThat(newLabel.text()).isEqualTo("a2_1"); + // But the item in the compartment does + InsideLabel itemLabel = diagramNavigator.nodeWithId(GeneralViewItemAndAttributeProjectData.GraphicalIds.A2_1_ICON_AND_LABEL_ID).getNode().getInsideLabel(); + assertThat(itemLabel.getText()).isEqualTo("in a2_1 = a1.a1_2"); }); - // But the item in the compartment does + // Direct edit initial text does not include the "= ..." part Runnable directEditInitialLabelCheck2 = this.directEditInitialLabelTester.checkDirectEditInitialLabelOnNode(diagram, GeneralViewItemAndAttributeProjectData.GraphicalIds.A2_1_ICON_AND_LABEL_ID, - "in a2_1 = a1.a1_2"); + "in a2_1"); // Simple test on the icon and label node representing a2_1 - Runnable directEditInsideLabelCheck2 = this.directEditTester.directEditInsideLabel(diagram, - GeneralViewItemAndAttributeProjectData.GraphicalIds.A2_1_ICON_AND_LABEL_ID, - "in a2_1 = a1.a1_3.i2_1.i3_1"); - Consumer directEditInsideLabelDiagramCheck2 = assertRefreshedDiagramThat(newDiagram -> { + Runnable editExpression = this.editExpression(GeneralViewItemAndAttributeProjectData.EDITING_CONTEXT_ID, () -> GeneralViewItemAndAttributeProjectData.SemanticIds.A2_1_ID, + "a1.a1_3.i2_1.i3_1"); + Consumer editExpressionDiagramCheck = assertRefreshedDiagramThat(newDiagram -> { DiagramNavigator diagramNavigator = new DiagramNavigator(newDiagram); InsideLabel newLabel = diagramNavigator.nodeWithId(GeneralViewItemAndAttributeProjectData.GraphicalIds.A2_1_ICON_AND_LABEL_ID).getNode().getInsideLabel(); assertThat(newLabel.getText()).isEqualTo("in a2_1 = a1.a1_3.i2_1.i3_1"); @@ -232,8 +258,8 @@ public void payloadFeatureChain() { .then(directEditOutsideLabelCheck1) .consumeNextWith(directEditOutsideLabelDiagramCheck1) .then(directEditInitialLabelCheck2) - .then(directEditInsideLabelCheck2) - .consumeNextWith(directEditInsideLabelDiagramCheck2) + .then(editExpression) + .consumeNextWith(editExpressionDiagramCheck) .thenCancel() .verify(Duration.ofSeconds(10)); } @@ -252,48 +278,48 @@ public void attributeWithBranketExpression() { "x1"); // Inside P1 there is no import so we need qualified name - Runnable directEditInsideLabelCheck1 = this.directEditTester.directEditInsideLabel(diagram, - GeneralViewItemAndAttributeProjectData.GraphicalIds.P1_X1_ID, - "a2_1 = 45 [SI::kg]"); - Consumer directEditInsideLabelDiagramCheck1 = assertRefreshedDiagramThat(newDiagram -> { + Runnable createExpression = this.createExpression(GeneralViewItemAndAttributeProjectData.EDITING_CONTEXT_ID, + GeneralViewItemAndAttributeProjectData.SemanticIds.P1_X1_ID, + "45 [SI::kg]"); + Consumer createExpressionDiagramCheck = assertRefreshedDiagramThat(newDiagram -> { DiagramNavigator diagramNavigator = new DiagramNavigator(newDiagram); InsideLabel newLabel = diagramNavigator.nodeWithId(GeneralViewItemAndAttributeProjectData.GraphicalIds.P1_X1_ID).getNode().getInsideLabel(); - assertThat(newLabel.getText()).isEqualTo("a2_1 = 45 [kg]"); // Use short name for displaying bracket expression + assertThat(newLabel.getText()).isEqualTo("x1 = 45 [kg]"); // Use short name for displaying bracket + // expression }); Runnable directEditInitialLabelCheck2 = this.directEditInitialLabelTester.checkDirectEditInitialLabelOnNode(diagram, GeneralViewItemAndAttributeProjectData.GraphicalIds.P1_X1_ID, - "a2_1 = 45 [kg]"); + "x1"); // Inside P1_1 there is an import of S1::* so no need to use the qualified name - // Test using qualified name + Runnable directEditInitialLabelCheck3 = this.directEditInitialLabelTester.checkDirectEditInitialLabelOnNode(diagram, GeneralViewItemAndAttributeProjectData.GraphicalIds.P1_1_X1_ID, "x1"); - // Inside P1 there is no import so we need qualified name - Runnable directEditInsideLabelCheck3 = this.directEditTester.directEditInsideLabel(diagram, - GeneralViewItemAndAttributeProjectData.GraphicalIds.P1_1_X1_ID, - "a2_1 = 45 [kg]"); - Consumer directEditInsideLabelDiagramCheck3 = assertRefreshedDiagramThat(newDiagram -> { + Runnable createExpression2 = this.createExpression(GeneralViewItemAndAttributeProjectData.EDITING_CONTEXT_ID, + GeneralViewItemAndAttributeProjectData.SemanticIds.P1_1_X1_ID, + "45 [kg]"); + Consumer createExpressionLabelDiagramCheck2 = assertRefreshedDiagramThat(newDiagram -> { DiagramNavigator diagramNavigator = new DiagramNavigator(newDiagram); InsideLabel newLabel = diagramNavigator.nodeWithId(GeneralViewItemAndAttributeProjectData.GraphicalIds.P1_1_X1_ID).getNode().getInsideLabel(); - assertThat(newLabel.getText()).isEqualTo("a2_1 = 45 [kg]"); + assertThat(newLabel.getText()).isEqualTo("x1 = 45 [kg]"); }); Runnable directEditInitialLabelCheck4 = this.directEditInitialLabelTester.checkDirectEditInitialLabelOnNode(diagram, GeneralViewItemAndAttributeProjectData.GraphicalIds.P1_1_X1_ID, - "a2_1 = 45 [kg]"); + "x1"); StepVerifier.create(flux) .consumeNextWith(initialDiagramContentConsumer) .then(directEditInitialLabelCheck1) - .then(directEditInsideLabelCheck1) - .consumeNextWith(directEditInsideLabelDiagramCheck1) + .then(createExpression) + .consumeNextWith(createExpressionDiagramCheck) .then(directEditInitialLabelCheck2) .then(directEditInitialLabelCheck3) - .then(directEditInsideLabelCheck3) - .consumeNextWith(directEditInsideLabelDiagramCheck3) + .then(createExpression2) + .consumeNextWith(createExpressionLabelDiagramCheck2) .then(directEditInitialLabelCheck4) .thenCancel() .verify(Duration.ofSeconds(10)); @@ -309,33 +335,32 @@ public void payloadFeatureChainBindingEdge() { Consumer initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram::set); // Create an edge using direct edit - Runnable directEditInsideLabelCheck1 = this.directEditTester.directEditInsideLabel(diagram, - GeneralViewItemAndAttributeProjectData.GraphicalIds.A2_1_ICON_AND_LABEL_ID, - "in a2_1 = a1.a1_1"); + Runnable createExpression = this.createExpression(GeneralViewItemAndAttributeProjectData.EDITING_CONTEXT_ID, + GeneralViewItemAndAttributeProjectData.SemanticIds.A2_1_ID, + "a1.a1_1"); Consumer diagramCheck1 = this.buildEdgeChecker(diagram, GeneralViewItemAndAttributeProjectData.GraphicalIds.A2_1_ICON_AND_LABEL_ID, "in a2_1 = a1.a1_1", GeneralViewItemAndAttributeProjectData.GraphicalIds.A2_1_BORDERED_NODE_ID, GeneralViewItemAndAttributeProjectData.GraphicalIds.A1_1_BORDERED_NODE_ID); // Change the edge to a new target - Runnable directEditInsideLabelCheck2 = this.directEditTester.directEditInsideLabel(diagram, - GeneralViewItemAndAttributeProjectData.GraphicalIds.A2_1_ICON_AND_LABEL_ID, - "in a2_1 = a1.a1_2"); + Runnable editExpression1 = this.editExpression(GeneralViewItemAndAttributeProjectData.EDITING_CONTEXT_ID, + () -> GeneralViewItemAndAttributeProjectData.SemanticIds.A2_1_ID, + "a1.a1_2"); Consumer diagramCheck2 = this.buildEdgeChecker(diagram, GeneralViewItemAndAttributeProjectData.GraphicalIds.A2_1_ICON_AND_LABEL_ID, "in a2_1 = a1.a1_2", GeneralViewItemAndAttributeProjectData.GraphicalIds.A2_1_BORDERED_NODE_ID, GeneralViewItemAndAttributeProjectData.GraphicalIds.A1_2_BORDERED_NODE_ID); // Remove edge - Runnable directEditInsideLabelCheck3 = this.directEditTester.directEditInsideLabel(diagram, - GeneralViewItemAndAttributeProjectData.GraphicalIds.A2_1_ICON_AND_LABEL_ID, - "in a2_1 ="); + Runnable deleteExpression = this.deleteExpression(GeneralViewItemAndAttributeProjectData.EDITING_CONTEXT_ID, + GeneralViewItemAndAttributeProjectData.SemanticIds.A2_1_ID); Consumer diagramCheck3 = this.buildNoEdgeStartingFromChecker(diagram, GeneralViewItemAndAttributeProjectData.GraphicalIds.A2_1_ICON_AND_LABEL_ID, "in a2_1", GeneralViewItemAndAttributeProjectData.GraphicalIds.A2_1_BORDERED_NODE_ID); StepVerifier.create(flux) .consumeNextWith(initialDiagramContentConsumer) - .then(directEditInsideLabelCheck1) + .then(createExpression) .consumeNextWith(diagramCheck1) - .then(directEditInsideLabelCheck2) + .then(editExpression1) .consumeNextWith(diagramCheck2) - .then(directEditInsideLabelCheck3) + .then(deleteExpression) .consumeNextWith(diagramCheck3) .thenCancel() .verify(Duration.ofSeconds(10)); @@ -405,4 +430,32 @@ private Consumer buildNoEdgeStartingFromChecker(AtomicReference assertThat(newEdges).hasSize(0); }); } + + private Runnable createExpression(String editingContextId, String parentElementId, String expressionContent) { + return () -> { + var input = new CreateExpressionInput(UUID.randomUUID(), editingContextId, parentElementId, expressionContent); + var result = this.createExpressionMutationRunner.run(input); + String typename = JsonPath.read(result.data(), "$.data.createExpression.__typename"); + assertThat(typename).isEqualTo(CreateExpressionSuccessPayload.class.getSimpleName()); + }; + } + + private Runnable editExpression(String editingContextId, Supplier elementId, String expressionContent) { + return () -> { + var input = new EditExpressionInput(UUID.randomUUID(), editingContextId, elementId.get(), expressionContent); + var result = this.editExpressionMutationRunner.run(input); + String typename = JsonPath.read(result.data(), "$.data.editExpression.__typename"); + assertThat(typename).isEqualTo(EditExpressionSuccessPayload.class.getSimpleName()); + }; + } + + private Runnable deleteExpression(String editingContextId, String parentElementId) { + return () -> { + var input = new DeleteExpressionInput(UUID.randomUUID(), editingContextId, parentElementId); + var result = this.deleteExpressionMutationRunner.run(input); + String typename = JsonPath.read(result.data(), "$.data.deleteExpression.__typename"); + assertThat(typename).isEqualTo(SuccessPayload.class.getSimpleName()); + }; + } + } diff --git a/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramMutationLabelService.java b/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramMutationLabelService.java index 0eb22af12..8331e0418 100644 --- a/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramMutationLabelService.java +++ b/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramMutationLabelService.java @@ -139,7 +139,7 @@ public Element directEditNode(Element element, String newLabel) { * @return the given {@link Element}. */ public Element directEditListItem(Element element, String newLabel) { - return this.directEdit(element, newLabel, true, (String[]) null); + return this.directEdit(element, newLabel, true, LabelService.VALUE_OFF); } public Element editMultiplicityRangeCenterLabel(Element element, String newLabel) { diff --git a/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramQueryLabelService.java b/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramQueryLabelService.java index 359571050..47a270612 100644 --- a/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramQueryLabelService.java +++ b/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramQueryLabelService.java @@ -360,7 +360,7 @@ private String getValueStringRepresentation(Usage usage, boolean directEditInput .filter(FeatureValue.class::isInstance) .map(FeatureValue.class::cast) .findFirst(); - if (featureValue.isPresent()) { + if (featureValue.isPresent() && !directEditInput) { var expression = featureValue.get().getValue(); String valueAsString = null; if (expression != null) { diff --git a/backend/services/syson-services/src/main/java/org/eclipse/syson/util/AQLUtils.java b/backend/services/syson-services/src/main/java/org/eclipse/syson/util/AQLUtils.java index 7c950781d..b6c471880 100644 --- a/backend/services/syson-services/src/main/java/org/eclipse/syson/util/AQLUtils.java +++ b/backend/services/syson-services/src/main/java/org/eclipse/syson/util/AQLUtils.java @@ -33,91 +33,6 @@ public static String aqlString(String string) { return '\'' + string + '\''; } - /** - * Returns the AQL expression for calling a service without any parameter using self as the - * instance.
- * For instance: aql:self.myService() - * - * @param serviceName - * The name of the service to call. - * @return An AQL expression (String) expressing the call of the given service. - */ - public static String getSelfServiceCallExpression(String serviceName) { - return getSelfServiceCallExpression(serviceName, List.of()); - } - - /** - * Returns the AQL expression for calling a service with one parameter using self as the instance.
- * For instance: aql:self.myService(sysml::Package) - * - * @param serviceName - * The name of the service to call. - * @param parameter - * The unique parameter. - * @return An AQL expression (String) expressing the call of the given service. - */ - public static String getSelfServiceCallExpression(String serviceName, String parameter) { - return getSelfServiceCallExpression(serviceName, List.of(parameter)); - } - - /** - * Returns the AQL expression for calling a service with several parameters using self as the - * instance.
- * For instance: aql:self.myService(sysml::Package, newValue) - * - * @param serviceName - * The name of the service to call. - * @param parameters - * The list of parameters. - * @return An AQL expression (String) expressing the call of the given service. - */ - public static String getSelfServiceCallExpression(String serviceName, List parameters) { - return AQLConstants.AQL_SELF + '.' + serviceName + '(' + String.join(",", parameters) + ')'; - } - - /** - * Returns the AQL expression for calling a service without any parameter. - * - * @param var - * The part of the expression specifying the variable on which the service should be called. - * @param serviceName - * The name of the service to call. - * @return An AQL expression (String) expressing the call of the given service. - */ - public static String getServiceCallExpression(String var, String serviceName) { - return AQLUtils.getServiceCallExpression(var, serviceName, List.of()); - } - - /** - * Returns the AQL expression for calling a service with one parameter. - * - * @param var - * The part of the expression specifying the variable on which the service should be called. - * @param serviceName - * The name of the service to call. - * @param parameter - * The unique parameter. - * @return An AQL expression (String) expressing the call of the given service. - */ - public static String getServiceCallExpression(String var, String serviceName, String parameter) { - return AQLUtils.getServiceCallExpression(var, serviceName, List.of(parameter)); - } - - /** - * Returns the AQL expression for calling a service with several parameters. - * - * @param var - * The part of the expression specifying the variable on which the service should be called. - * @param serviceName - * The name of the service to call. - * @param parameters - * The list of parameters. - * @return An AQL expression (String) expressing the call of the given service. - */ - public static String getServiceCallExpression(String var, String serviceName, List parameters) { - return AQLConstants.AQL + var + '.' + serviceName + '(' + String.join(",", parameters) + ')'; - } - /** * Returns an AQL sequence from the given members. * diff --git a/backend/services/syson-services/src/main/java/org/eclipse/syson/util/SysONEcoreUtil.java b/backend/services/syson-services/src/main/java/org/eclipse/syson/util/SysONEcoreUtil.java index 4a07ae587..a45782004 100644 --- a/backend/services/syson-services/src/main/java/org/eclipse/syson/util/SysONEcoreUtil.java +++ b/backend/services/syson-services/src/main/java/org/eclipse/syson/util/SysONEcoreUtil.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2025 Obeo. + * Copyright (c) 2025, 2026 Obeo. * This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at @@ -14,7 +14,6 @@ import java.util.Collection; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; @@ -24,7 +23,6 @@ import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.InternalEObject; import org.eclipse.emf.ecore.util.EcoreUtil; -import org.eclipse.emf.ecore.util.FeatureMapUtil; /** * SysON specialization of {@link EcoreUtil} class coming from org.eclipse.emf.ecore. The specialization allows to have @@ -93,24 +91,4 @@ public static void deleteAll(Collection eObjects, boolean rec EcoreUtil.remove(crossResourceEObject.eContainer(), crossResourceEObject.eContainmentFeature(), crossResourceEObject); } } - - /** - * Removes the values from the setting. - * - * @param setting - * the setting holding the value. - * @param values - * the values to remove. - */ - public static void removeAll(EStructuralFeature.Setting setting, Collection values) { - EStructuralFeature eStructuralFeature = setting.getEStructuralFeature(); - if (eStructuralFeature != null && !eStructuralFeature.isDerived()) { - if (FeatureMapUtil.isMany(setting.getEObject(), eStructuralFeature)) { - ((List) setting.get(false)).removeAll(values); - } else { - // The feature is assumed to hold the value. - setting.unset(); - } - } - } } diff --git a/doc/content/modules/user-manual/pages/hands-on/how-tos/model-management.adoc b/doc/content/modules/user-manual/pages/hands-on/how-tos/model-management.adoc index f8fb1ea40..e3146a5e7 100644 --- a/doc/content/modules/user-manual/pages/hands-on/how-tos/model-management.adoc +++ b/doc/content/modules/user-manual/pages/hands-on/how-tos/model-management.adoc @@ -303,9 +303,6 @@ If _SomeTypeName_ doesn't exist, it's created as an appropriate type element (fo * Typing `someElement:>otherElement` on a element sets _someElement_ as a `Subsetting` of the _otherElement_ if both element are of the same type. Otherwise, it creates a `specialization` relationship between _someElement_ and _otherElement_. * Typing `someElement:>>otherElement` on an element sets _someElement_ as a `redefinition` of _otherElement_. -* On a `Feature`, typing `= someExpression` sets a bounded value to the feature (at the moment only `LiteralExpression` are handled in the expression part). -* On a `Feature`, typing `:= someExpression` sets an initial value to the feature (at the moment only `LiteralExpression` are handled in the expression part). -* On a `Feature`, typing the symbol `default` before the symbols `=` or `:=` makes the bounded or initial value "default" (to be opposed as "fixed" if the symbol is not present). These shortcuts offer a convenient way to update model elements efficiently. diff --git a/doc/content/modules/user-manual/pages/hands-on/tutorials/flashlight.adoc b/doc/content/modules/user-manual/pages/hands-on/tutorials/flashlight.adoc index b1754803c..7cab41eb3 100644 --- a/doc/content/modules/user-manual/pages/hands-on/tutorials/flashlight.adoc +++ b/doc/content/modules/user-manual/pages/hands-on/tutorials/flashlight.adoc @@ -72,7 +72,10 @@ Then select `SI/Library Package SI` to import it from the standard libraries. This will allow you to use standard units, such as the kilogram. . Click on the `Manage Visibility -> require constraints` (i.e. the eye icon that will appear when the mouse pointer will be over the `weight` requirement) on the `weight` requirement to display the _require constraints_ compartment on the `weight` requirement. . Use the palette filter to locate the `New Required Constraint` tool, and apply it to the `weight` requirement. -. In the `require constraints` compartment of the `weight` requirement, edit the constraint to: `actualweight \<= 0.25 [kg]` +. In the dialog that opens, choose the `Create a New Require Constraint` option and then `Confirm`. +. In the `require constraints` compartment of the `weight` requirement, select the newly created constraint. +. Open it's palette, and invoke the `New Expression` tool in the `Expression` section. +. In the modal that opens, enter `actualweight \<= 0.25 [kg]` and click on the `Update` button. This will set the predicate of the constraint. image::flashlight-requirements-tree.png[Flashlight Requirements Tree] @@ -117,12 +120,16 @@ action directLight{ ---- . In the `action flow` compartment of `produceDirectLight` use the tool `Related Elements -> Add existing element`. . Create a new transition between `fork1` and `connectDCPwr`. -. Set the value of `connectDCPwr::onOffCmd` by editing the item `in onOffCmd` of `connectDCPwr` to `in onOffCmd = produceDirectedLight.onOffCmd`. Do that in the _items_ compartment of the `connectDCPwr` action in the diagram. +. In the _items_ compartment of the `connectDCPwr` action in the diagram, select the item `onOffCmd` and invoke the `New Expression` tool on it. +. In the expression dialog, enter `produceDirectedLight.onOffCmd` and click on `Update`. +. The `in connectDCPwr` item now shows as `in connectDCPwr = produceDirectedLight.onOffCmd` and an edge has appeared to show the binding. . Create a `Flow Connection (flow)` from `dcPwrOut` of `connectDCPwr` to `dcPwrIn` of `generateLight`. . Create a `Flow Connection (flow)` between `light` of `generateLight` to `lightIn` of `directLight`. . Create a `Flow Connection (flow)` between `outdcPwr` of `provideDCPwr` to `dcPwrIn` of `connectDCPwr`. . Use the eye icon on the `produceDirectLight` action to display the `items` compartment. -. Set the value of `produceDirectedLight::lightOut` by renaming the item `out lightOut` of `produceDirectedLight` to `out lightOut = directLight.lightOut`. +. In the _items_ compartment of the `produceDirectedLight` action in the diagram, select the item `out lightOut` and invoke the `New Expression` tool on it. +. In the expression dialog, enter `directLight.lightOut` and click on `Update`. +. The `out lightOut` item now shows as `out lightOut = directLight.lightOut` and an edge has appeared to show the binding. image::flashlight-action-tree.png[Flashlight Requirements Tree] diff --git a/doc/content/modules/user-manual/pages/release-notes/2026.7.0.adoc b/doc/content/modules/user-manual/pages/release-notes/2026.7.0.adoc index e4fb2aa55..52f96f0fa 100644 --- a/doc/content/modules/user-manual/pages/release-notes/2026.7.0.adoc +++ b/doc/content/modules/user-manual/pages/release-notes/2026.7.0.adoc @@ -43,6 +43,9 @@ image::release-notes-assume-and-require-edges.png[assume and require edge betwee + image::release-notes-assume-and-require-edge-tools.png[list available edge tools containing the two new tools, between a RequirementUsage and a ConstraintUsage, width=60%,height=60%] +** When using direct edit on feature elements which have a value expression (e.g. an `Attribute` with a default value defined), the expression part (after the `=` sign) is no longer part of the initial direct edit value, and will be ignored if supplied in the new text value. +Users should use the dedicated tools to edit expressions instead, as they ensure only valid expressions (with all names resolving) are accepted. + * In the _Explorer_ view: ** The tree items corresponding to the internals of `Expression` elements (syntax tree) are now hidden by default. diff --git a/scripts/check-coverage.jsh b/scripts/check-coverage.jsh index 564f813ca..3d900cf78 100755 --- a/scripts/check-coverage.jsh +++ b/scripts/check-coverage.jsh @@ -36,7 +36,7 @@ var moduleCoverageData = List.of( new ModuleCoverage("syson-common-view", 100.0), new ModuleCoverage("syson-diagram-common-view", 89.0), new ModuleCoverage("syson-diagram-services", 85.0), - new ModuleCoverage("syson-direct-edit-grammar", 67.0), + new ModuleCoverage("syson-direct-edit-grammar", 66.0), new ModuleCoverage("syson-form-services", 100.0), new ModuleCoverage("syson-model-services", 95.0), new ModuleCoverage("syson-representation-services", 100.0),