From 3587435c84b2036bdb64b2d29fe1f6951015db89 Mon Sep 17 00:00:00 2001 From: Pierre-Charles David Date: Thu, 18 Jun 2026 17:21:35 +0200 Subject: [PATCH 1/2] [2289] Fix expression support for TransitionUsage Bug: https://github.com/eclipse-syson/syson/issues/2289 Signed-off-by: Pierre-Charles David --- CHANGELOG.adoc | 1 + .../SysMLv2PropertiesConfigurer.java | 24 +++++++++++++++++++ .../services/DetailsViewService.java | 18 ++++++++++++++ .../cypress/e2e/project/details/details.cy.ts | 14 +++++++++++ 4 files changed, 57 insertions(+) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index c9506e47f..1b3ad10d8 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -31,6 +31,7 @@ It has long been unused by Sirius Web itself (since the transition to MUI). - https://github.com/eclipse-syson/syson/issues/2237[#2237] [diagrams] Fix the item label inside `frames`, `require constraints`, and `assume constraints` compartments. - https://github.com/eclipse-syson/syson/issues/2278[#2278] [diagrams] Display inherited behavior parameters in the _parameters_ compartment of `ActionDefinition` and `ActionUsage` graphical nodes. - https://github.com/eclipse-syson/syson/issues/2291[#2291] [diagrams] Fix item label for constraint to show their name and expression value between braces (if present). +- https://github.com/eclipse-syson/syson/issues/2289[#2289] [details] Fix expression support for `TransitionUsage`. === Improvements diff --git a/backend/application/syson-application-configuration/src/main/java/org/eclipse/syson/application/configuration/SysMLv2PropertiesConfigurer.java b/backend/application/syson-application-configuration/src/main/java/org/eclipse/syson/application/configuration/SysMLv2PropertiesConfigurer.java index 9c51c98d1..007a9e5b5 100644 --- a/backend/application/syson-application-configuration/src/main/java/org/eclipse/syson/application/configuration/SysMLv2PropertiesConfigurer.java +++ b/backend/application/syson-application-configuration/src/main/java/org/eclipse/syson/application/configuration/SysMLv2PropertiesConfigurer.java @@ -193,6 +193,7 @@ private FormDescription createDetailsViewForElement() { pageCore.getGroups().add(this.createFeatureValuePropertiesGroup()); pageCore.getGroups().add(this.createExpressionPropertiesGroup()); pageCore.getGroups().add(this.createResultExpressionPropertiesGroup()); + pageCore.getGroups().add(this.createGuardExpressionPropertiesGroup()); PageDescription pageAdvanced = FormFactory.eINSTANCE.createPageDescription(); pageAdvanced.setName("SysON-DetailsView-Advanced"); @@ -276,6 +277,29 @@ private GroupDescription createResultExpressionPropertiesGroup() { return group; } + /** + * Creates a group to display the value of a guard expression. + * + * @return a {@link GroupDescription} + */ + private GroupDescription createGuardExpressionPropertiesGroup() { + GroupDescription group = FormFactory.eINSTANCE.createGroupDescription(); + group.setDisplayMode(GroupDisplayMode.LIST); + group.setName("Guard"); + group.setLabelExpression(""); + group.setSemanticCandidatesExpression(ServiceMethod.of0(DetailsViewService::getGuardExpression).aqlSelf()); + + TextAreaDescription expressionWidget = FormFactory.eINSTANCE.createTextAreaDescription(); + expressionWidget.setName("Guard"); + expressionWidget.setLabelExpression(CUSTOM_EXPRESSION_WIDGET_KEY); + expressionWidget.setValueExpression(ServiceMethod.of0(DetailsViewService::getExpressionTextualRepresentation).aqlSelf()); + expressionWidget.setIsEnabledExpression(AQLConstants.AQL_FALSE); + + group.getChildren().add(expressionWidget); + + return group; + } + private GroupDescription createCorePropertiesGroup() { GroupDescription group = FormFactory.eINSTANCE.createGroupDescription(); group.setDisplayMode(GroupDisplayMode.LIST); diff --git a/backend/application/syson-application-configuration/src/main/java/org/eclipse/syson/application/services/DetailsViewService.java b/backend/application/syson-application-configuration/src/main/java/org/eclipse/syson/application/services/DetailsViewService.java index a7c338ce1..4b418cb84 100644 --- a/backend/application/syson-application-configuration/src/main/java/org/eclipse/syson/application/services/DetailsViewService.java +++ b/backend/application/syson-application-configuration/src/main/java/org/eclipse/syson/application/services/DetailsViewService.java @@ -68,6 +68,7 @@ import org.eclipse.syson.sysml.Succession; import org.eclipse.syson.sysml.SysmlFactory; import org.eclipse.syson.sysml.SysmlPackage; +import org.eclipse.syson.sysml.TransitionFeatureMembership; import org.eclipse.syson.sysml.TransitionUsage; import org.eclipse.syson.sysml.Type; import org.eclipse.syson.sysml.Usage; @@ -651,6 +652,23 @@ public Element getResultExpression(Element self) { return result; } + /** + * Gets the guard expression from a {@ink TransitionUsage} or {@link TransitionFeatureMembership}. + * + * @param self + * {@ink TransitionUsage} or {@link TransitionFeatureMembership}. + * @return the corresponding guard {@link Expression} or null + */ + public Expression getGuardExpression(Element self) { + Expression result = null; + if (self instanceof TransitionFeatureMembership expressionMembership && expressionMembership.getOwnedMemberElement() instanceof Expression expression) { + result = expression; + } else if (self instanceof TransitionUsage transitionUsage && !transitionUsage.getGuardExpression().isEmpty()) { + result = transitionUsage.getGuardExpression().get(0); + } + return result; + } + /** * Gets the {@link ResultExpressionMembership} from a {@link Namespace} or a {@link ResultExpressionMembership}. * diff --git a/integration-tests-cypress/cypress/e2e/project/details/details.cy.ts b/integration-tests-cypress/cypress/e2e/project/details/details.cy.ts index 150ca4f8b..38e210425 100644 --- a/integration-tests-cypress/cypress/e2e/project/details/details.cy.ts +++ b/integration-tests-cypress/cypress/e2e/project/details/details.cy.ts @@ -110,5 +110,19 @@ describe('Details View Tests', () => { cy.getByTestId('details-expression-value').should('have.text', '80 [SI::centimetre]'); }); }); + + context('When we select a TransitionUsage with a guard Expression', () => { + beforeEach(() => { + explorer.select('Drive Batmobile'); + explorer.expand('Drive Batmobile'); + // The TransitionUsage is anonymous so select the closest named item and move down using the keyboard + explorer.select('scanEnvironment'); + cy.getByTestId('scanEnvironment').type('{downArrow}{downArrow}{downArrow}'); + }); + it("Then the Details view shows the child expression's textual value", () => { + cy.getByTestId('details-expression-value').should('exist'); + cy.getByTestId('details-expression-value').should('have.text', 'scanEnvironment.status == StatusKind::safe'); + }); + }); }); }); From 7f5e648683a00238836417e812ad88ce93d77c11 Mon Sep 17 00:00:00 2001 From: Pierre-Charles David Date: Mon, 22 Jun 2026 17:01:35 +0200 Subject: [PATCH 2/2] [cleanup] Simplify the expression details widget definition and rendering - Share common code in the backend. - Produce an actual Label widget instead of a Textfield as this is how we render it now. - Simplify the frontend component to be closer to the LabelWidgetPropertySection instead of the more complex TextfieldPropertySection. Signed-off-by: Pierre-Charles David --- .../SysMLv2PropertiesConfigurer.java | 80 ++++++----------- .../expressions/ExpressionPropertySection.tsx | 87 ++++++------------- .../registry/SysONExtensionRegistry.tsx | 2 +- 3 files changed, 54 insertions(+), 115 deletions(-) diff --git a/backend/application/syson-application-configuration/src/main/java/org/eclipse/syson/application/configuration/SysMLv2PropertiesConfigurer.java b/backend/application/syson-application-configuration/src/main/java/org/eclipse/syson/application/configuration/SysMLv2PropertiesConfigurer.java index 007a9e5b5..71c630df0 100644 --- a/backend/application/syson-application-configuration/src/main/java/org/eclipse/syson/application/configuration/SysMLv2PropertiesConfigurer.java +++ b/backend/application/syson-application-configuration/src/main/java/org/eclipse/syson/application/configuration/SysMLv2PropertiesConfigurer.java @@ -209,49 +209,23 @@ private FormDescription createDetailsViewForElement() { } /** - * Creates a group to display the value of an Expression. + * Creates a group to display the value of an actual Expression (selected directly). * * @return a {@link GroupDescription} */ private GroupDescription createExpressionPropertiesGroup() { - GroupDescription group = FormFactory.eINSTANCE.createGroupDescription(); - group.setDisplayMode(GroupDisplayMode.LIST); - group.setName("Expression Value"); - group.setLabelExpression(""); - group.setSemanticCandidatesExpression(ServiceMethod.of0(DetailsViewService::getExpression).aqlSelf()); - - TextAreaDescription expressionWidget = FormFactory.eINSTANCE.createTextAreaDescription(); - expressionWidget.setName("Expression"); - expressionWidget.setLabelExpression(CUSTOM_EXPRESSION_WIDGET_KEY); - expressionWidget.setValueExpression(ServiceMethod.of0(DetailsViewService::getExpressionTextualRepresentation).aqlSelf()); - expressionWidget.setIsEnabledExpression(AQLConstants.AQL_FALSE); - - group.getChildren().add(expressionWidget); - - return group; + return this.createExpressionPropertiesGroup(ServiceMethod.of0(DetailsViewService::getExpression).aqlSelf(), + ServiceMethod.of0(DetailsViewService::getExpressionTextualRepresentation).aqlSelf()); } /** - * Creates a group to display the value of a Feature or FeatureValue. + * Creates a group to display the Expression defining the value of a Feature or FeatureValue. * * @return a {@link GroupDescription} */ private GroupDescription createFeatureValuePropertiesGroup() { - GroupDescription group = FormFactory.eINSTANCE.createGroupDescription(); - group.setDisplayMode(GroupDisplayMode.LIST); - group.setName("Value"); - group.setLabelExpression(""); - group.setSemanticCandidatesExpression(ServiceMethod.of0(DetailsViewService::getFeatureValue).aqlSelf()); - - TextAreaDescription expressionWidget = FormFactory.eINSTANCE.createTextAreaDescription(); - expressionWidget.setName("ValueExpression"); - expressionWidget.setLabelExpression(CUSTOM_EXPRESSION_WIDGET_KEY); - expressionWidget.setValueExpression(ServiceMethod.of0(DetailsViewService::getValueExpressionTextualRepresentation).aqlSelf()); - expressionWidget.setIsEnabledExpression(AQLConstants.AQL_FALSE); - - group.getChildren().add(expressionWidget); - - return group; + return this.createExpressionPropertiesGroup(ServiceMethod.of0(DetailsViewService::getFeatureValue).aqlSelf(), + ServiceMethod.of0(DetailsViewService::getValueExpressionTextualRepresentation).aqlSelf()); } /** @@ -260,21 +234,8 @@ private GroupDescription createFeatureValuePropertiesGroup() { * @return a {@link GroupDescription} */ private GroupDescription createResultExpressionPropertiesGroup() { - GroupDescription group = FormFactory.eINSTANCE.createGroupDescription(); - group.setDisplayMode(GroupDisplayMode.LIST); - group.setName("Result"); - group.setLabelExpression(""); - group.setSemanticCandidatesExpression(ServiceMethod.of0(DetailsViewService::getResultExpression).aqlSelf()); - - TextAreaDescription expressionWidget = FormFactory.eINSTANCE.createTextAreaDescription(); - expressionWidget.setName("ResultExpression"); - expressionWidget.setLabelExpression(CUSTOM_EXPRESSION_WIDGET_KEY); - expressionWidget.setValueExpression(ServiceMethod.of0(DetailsViewService::getResultExpressionTextualRepresentation).aqlSelf()); - expressionWidget.setIsEnabledExpression(AQLConstants.AQL_FALSE); - - group.getChildren().add(expressionWidget); - - return group; + return this.createExpressionPropertiesGroup(ServiceMethod.of0(DetailsViewService::getResultExpression).aqlSelf(), + ServiceMethod.of0(DetailsViewService::getResultExpressionTextualRepresentation).aqlSelf()); } /** @@ -283,17 +244,30 @@ private GroupDescription createResultExpressionPropertiesGroup() { * @return a {@link GroupDescription} */ private GroupDescription createGuardExpressionPropertiesGroup() { + return this.createExpressionPropertiesGroup(ServiceMethod.of0(DetailsViewService::getGuardExpression).aqlSelf(), + ServiceMethod.of0(DetailsViewService::getExpressionTextualRepresentation).aqlSelf()); + } + + /** + * Helper to create a group to display an existing expression. + * + * @param semanticCandidatesExpression + * the AQL expression to find the semantic candidate. + * @param valueExpression + * the AQL expression to render the text of the expression corresponding to the semantic candidate. + * @return a {@link GroupDescription} + */ + private GroupDescription createExpressionPropertiesGroup(String semanticCandidatesExpression, String valueExpression) { GroupDescription group = FormFactory.eINSTANCE.createGroupDescription(); group.setDisplayMode(GroupDisplayMode.LIST); - group.setName("Guard"); + group.setName("Expression Value"); group.setLabelExpression(""); - group.setSemanticCandidatesExpression(ServiceMethod.of0(DetailsViewService::getGuardExpression).aqlSelf()); + group.setSemanticCandidatesExpression(semanticCandidatesExpression); - TextAreaDescription expressionWidget = FormFactory.eINSTANCE.createTextAreaDescription(); - expressionWidget.setName("Guard"); + LabelDescription expressionWidget = FormFactory.eINSTANCE.createLabelDescription(); + expressionWidget.setName("Expression"); expressionWidget.setLabelExpression(CUSTOM_EXPRESSION_WIDGET_KEY); - expressionWidget.setValueExpression(ServiceMethod.of0(DetailsViewService::getExpressionTextualRepresentation).aqlSelf()); - expressionWidget.setIsEnabledExpression(AQLConstants.AQL_FALSE); + expressionWidget.setValueExpression(valueExpression); group.getChildren().add(expressionWidget); diff --git a/frontend/syson-components/src/extensions/expressions/ExpressionPropertySection.tsx b/frontend/syson-components/src/extensions/expressions/ExpressionPropertySection.tsx index d9400084a..2ae67f25e 100644 --- a/frontend/syson-components/src/extensions/expressions/ExpressionPropertySection.tsx +++ b/frontend/syson-components/src/extensions/expressions/ExpressionPropertySection.tsx @@ -13,12 +13,12 @@ import { getCSSColor } from '@eclipse-sirius/sirius-components-core'; import { - GQLTextarea, - GQLTextfield, + getTextDecorationLineValue, + GQLLabelWidget, + LabelStyleProps, PropertySectionComponent, PropertySectionComponentProps, PropertySectionLabel, - TextfieldStyleProps, } from '@eclipse-sirius/sirius-components-forms'; import Typography from '@mui/material/Typography'; @@ -29,57 +29,24 @@ import { useState } from 'react'; import { makeStyles } from 'tss-react/mui'; import { EditSysMLExpressionModal } from './EditSysMLExpressionModal'; -const useStyle = makeStyles()( - (theme, { backgroundColor, foregroundColor, fontSize, italic, bold, gridLayout }) => { - const { - gridTemplateColumns, - gridTemplateRows, - labelGridColumn, - labelGridRow, - widgetGridColumn, - widgetGridRow, - gap, - } = { - ...gridLayout, - }; - return { - style: { - backgroundColor: backgroundColor ? getCSSColor(backgroundColor, theme) : undefined, - color: foregroundColor ? getCSSColor(foregroundColor, theme) : undefined, - fontSize: fontSize ? fontSize : undefined, - fontStyle: italic ? 'italic' : undefined, - fontWeight: bold ? 'bold' : undefined, - }, - input: { - paddingTop: theme.spacing(0.5), - paddingBottom: theme.spacing(0.5), - }, - textfield: { - marginTop: theme.spacing(0.5), - marginBottom: theme.spacing(0.5), - }, - formControl: {}, - propertySection: { - display: 'grid', - gridTemplateColumns, - gridTemplateRows, - alignItems: 'center', - gap: gap ?? '', - }, - propertySectionLabel: { - gridColumn: labelGridColumn, - gridRow: labelGridRow, - display: 'flex', - flexDirection: 'row', - gap: theme.spacing(2), - alignItems: 'center', - }, - propertySectionWidget: { - gridColumn: widgetGridColumn, - gridRow: widgetGridRow, - }, - }; - } +const useStyle = makeStyles()( + (theme, { color, fontSize, italic, bold, underline, strikeThrough }) => ({ + style: { + color: color ? getCSSColor(color, theme) : undefined, + fontSize: fontSize ? fontSize : undefined, + fontStyle: italic ? 'italic' : undefined, + fontWeight: bold ? 'bold' : undefined, + textDecorationLine: getTextDecorationLineValue(underline, strikeThrough), + verticalAlign: 'baseline', + alignItems: 'center', + display: 'flex', + }, + propertySection: { + display: 'flex', + flexDirection: 'row', + gap: theme.spacing(2), + }, + }) ); // Extracts the UUID from the string of the form "details://?objectIds=[c5f78f3a-8b39-4cb0-903a-cedd8e6e71f6]" if it contains a single UUID, otherwise returns null. @@ -93,20 +60,18 @@ type ExpressionPropertySectionState = { state: 'idle' | 'modal'; }; -export const ExpressionPropertySection: PropertySectionComponent = ({ +export const ExpressionPropertySection: PropertySectionComponent = ({ editingContextId, formId, widget, -}: PropertySectionComponentProps) => { - const props: TextfieldStyleProps = { - backgroundColor: widget.style?.backgroundColor ?? null, - foregroundColor: widget.style?.foregroundColor ?? null, +}: PropertySectionComponentProps) => { + const props: LabelStyleProps = { + color: widget.style?.color ?? null, fontSize: widget.style?.fontSize ?? null, italic: widget.style?.italic ?? null, bold: widget.style?.bold ?? null, underline: widget.style?.underline ?? null, strikeThrough: widget.style?.strikeThrough ?? null, - gridLayout: widget.style?.widgetGridLayout ?? null, }; const { classes } = useStyle(props); const [state, setState] = useState({ @@ -137,7 +102,7 @@ export const ExpressionPropertySection: PropertySectionComponent -
+
diff --git a/frontend/syson-components/src/extensions/registry/SysONExtensionRegistry.tsx b/frontend/syson-components/src/extensions/registry/SysONExtensionRegistry.tsx index c89a4e42b..5a3a94be3 100644 --- a/frontend/syson-components/src/extensions/registry/SysONExtensionRegistry.tsx +++ b/frontend/syson-components/src/extensions/registry/SysONExtensionRegistry.tsx @@ -239,7 +239,7 @@ sysONExtensionRegistry.putData(widgetContributionExtensionPoint, { previewComponent: () => null, component: (widget: GQLWidget): PropertySectionComponent | null => { let propertySectionComponent: PropertySectionComponent | null = null; - if (widget.__typename == 'Textarea' && widget.label.startsWith('syson:expression-value-widget')) { + if (widget.__typename == 'LabelWidget' && widget.label.startsWith('syson:expression-value-widget')) { propertySectionComponent = ExpressionPropertySection as PropertySectionComponent; } return propertySectionComponent;