Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ A new compartment named _satisfy requirements_ has also been added to `PartDefin
- https://github.com/eclipse-syson/syson/issues/1395[#1395] Provide a way to duplicate a semantic element ans its representation node in the _General View_ diagram.
- https://github.com/eclipse-syson/syson/issues/1740[#1740] [diagrams] Add _items_ compartment and graphical border node on `PortDefinition` graphical nodes in the _General View_ diagram.
- https://github.com/eclipse-syson/syson/issues/1786[#1786] [export] Implement textual export of `RequirementConstraintMembership`.
- https://github.com/eclipse-syson/syson/issues/1771[#1740] [services] Add `IDetailViewHelpTextProvider` service in order to add help text to properties widgets

== v2025.12.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import org.eclipse.sirius.components.view.widget.reference.ReferenceFactory;
import org.eclipse.sirius.components.view.widget.reference.ReferenceWidgetDescription;
import org.eclipse.syson.application.services.DetailsViewService;
import org.eclipse.syson.form.services.api.IDetailsViewHelpTextProvider;
import org.eclipse.syson.form.services.aql.FormMutationAQLService;
import org.eclipse.syson.form.services.aql.FormQueryAQLService;
import org.eclipse.syson.model.services.ModelMutationElementService;
Expand All @@ -76,6 +77,8 @@ public class SysMLv2PropertiesConfigurer implements IPropertiesDescriptionRegist

private static final String GET_DETAILS_VIEW_LABEL_SERVICE = "getDetailsViewLabel";

private static final String GET_DETAILS_VIEW_HELP_TEXT_SERVICE = "getDetailsViewHelpText";

private static final String CORE_PROPERTIES = "Core Properties";

private static final String ADVANCED_PROPERTIES = "Advanced Properties";
Expand Down Expand Up @@ -118,13 +121,16 @@ public class SysMLv2PropertiesConfigurer implements IPropertiesDescriptionRegist

private final IReadOnlyObjectPredicate readOnlyObjectPredicate;

private final List<IDetailsViewHelpTextProvider> detailViewHelpTextProviders;

public SysMLv2PropertiesConfigurer(List<Descriptor> composedAdapterFactoryDescriptors, ViewFormDescriptionConverter converter, IFeedbackMessageService feedbackMessageService,
ILabelService labelService, final IReadOnlyObjectPredicate readOnlyObjectPredicate) {
ILabelService labelService, List<IDetailsViewHelpTextProvider> detailViewHelpTextProviders, final IReadOnlyObjectPredicate readOnlyObjectPredicate) {
this.composedAdapterFactoryDescriptors = Objects.requireNonNull(composedAdapterFactoryDescriptors);
this.converter = Objects.requireNonNull(converter);
this.feedbackMessageService = Objects.requireNonNull(feedbackMessageService);
this.labelService = Objects.requireNonNull(labelService);
this.readOnlyObjectPredicate = Objects.requireNonNull(readOnlyObjectPredicate);
this.detailViewHelpTextProviders = Objects.requireNonNull(detailViewHelpTextProviders);
this.utilService = new UtilService();
}

Expand All @@ -147,7 +153,7 @@ public void addPropertiesDescriptions(IPropertiesDescriptionRegistry registry) {

// Convert the View-based FormDescription and register the result into the system
AQLInterpreter interpreter = new AQLInterpreter(List.of(),
List.of(new DetailsViewService(this.composedAdapterFactoryDescriptors, this.feedbackMessageService, this.readOnlyObjectPredicate), this.labelService, this.utilService,
List.of(new DetailsViewService(this.composedAdapterFactoryDescriptors, this.feedbackMessageService, this.readOnlyObjectPredicate, this.detailViewHelpTextProviders), this.labelService, this.utilService,
new ModelMutationAQLService(new ModelMutationElementService()), new ModelQueryAQLService(), new FormMutationAQLService(), new FormQueryAQLService()),
List.of(SysmlPackage.eINSTANCE));
ViewConverterResult converterResult = this.converter.convert(viewFormDescription, List.of(), interpreter);
Expand Down Expand Up @@ -548,6 +554,7 @@ private WidgetDescription createLabelWidget() {
LabelDescription label = FormFactory.eINSTANCE.createLabelDescription();
label.setName("LabelWidget");
label.setLabelExpression(AQLUtils.getSelfServiceCallExpression(GET_DETAILS_VIEW_LABEL_SERVICE, E_STRUCTURAL_FEATURE));
label.setHelpExpression(AQLUtils.getSelfServiceCallExpression(GET_DETAILS_VIEW_HELP_TEXT_SERVICE, E_STRUCTURAL_FEATURE));
label.setValueExpression(AQLUtils.getSelfServiceCallExpression("eGet", E_STRUCTURAL_FEATURE));
return label;
}
Expand All @@ -556,6 +563,7 @@ private WidgetDescription createTextAreaFieldWidget() {
TextAreaDescription textArea = FormFactory.eINSTANCE.createTextAreaDescription();
textArea.setName("TextAreaWidget");
textArea.setLabelExpression(AQLUtils.getSelfServiceCallExpression(GET_DETAILS_VIEW_LABEL_SERVICE, E_STRUCTURAL_FEATURE));
textArea.setHelpExpression(AQLUtils.getSelfServiceCallExpression(GET_DETAILS_VIEW_HELP_TEXT_SERVICE, E_STRUCTURAL_FEATURE));
textArea.setValueExpression(AQLUtils.getSelfServiceCallExpression("eGet", E_STRUCTURAL_FEATURE));
textArea.setIsEnabledExpression(AQL_NOT_SELF_IS_READ_ONLY_E_STRUCTURAL_FEATURE);
ChangeContext setNewValueOperation = ViewFactory.eINSTANCE.createChangeContext();
Expand All @@ -568,6 +576,7 @@ private WidgetDescription createTextfieldWidget() {
TextfieldDescription textfield = FormFactory.eINSTANCE.createTextfieldDescription();
textfield.setName("TextfieldWidget");
textfield.setLabelExpression(AQLUtils.getSelfServiceCallExpression(GET_DETAILS_VIEW_LABEL_SERVICE, E_STRUCTURAL_FEATURE));
textfield.setHelpExpression(AQLUtils.getSelfServiceCallExpression(GET_DETAILS_VIEW_HELP_TEXT_SERVICE, E_STRUCTURAL_FEATURE));
textfield.setValueExpression(AQLUtils.getSelfServiceCallExpression("eGet", E_STRUCTURAL_FEATURE));
textfield.setIsEnabledExpression(AQL_NOT_SELF_IS_READ_ONLY_E_STRUCTURAL_FEATURE);
ChangeContext setNewValueOperation = ViewFactory.eINSTANCE.createChangeContext();
Expand All @@ -581,6 +590,7 @@ private WidgetDescription createCheckboxWidget() {
checkbox.setName("CheckboxWidget");
checkbox.setLabelExpression(AQLUtils.getSelfServiceCallExpression(GET_DETAILS_VIEW_LABEL_SERVICE, E_STRUCTURAL_FEATURE));
checkbox.setValueExpression(AQLUtils.getSelfServiceCallExpression("eGet", E_STRUCTURAL_FEATURE));
checkbox.setHelpExpression(AQLUtils.getSelfServiceCallExpression(GET_DETAILS_VIEW_HELP_TEXT_SERVICE, E_STRUCTURAL_FEATURE));
checkbox.setIsEnabledExpression(AQL_NOT_SELF_IS_READ_ONLY_E_STRUCTURAL_FEATURE);
ChangeContext setNewValueOperation = ViewFactory.eINSTANCE.createChangeContext();
setNewValueOperation.setExpression(AQLUtils.getSelfServiceCallExpression("setNewValue", List.of(E_STRUCTURAL_FEATURE, ViewFormDescriptionConverter.NEW_VALUE)));
Expand All @@ -592,6 +602,7 @@ private WidgetDescription createRadioWidget() {
RadioDescription radio = FormFactory.eINSTANCE.createRadioDescription();
radio.setName("RadioWidget");
radio.setLabelExpression(AQLUtils.getSelfServiceCallExpression(GET_DETAILS_VIEW_LABEL_SERVICE, E_STRUCTURAL_FEATURE));
radio.setHelpExpression(AQLUtils.getSelfServiceCallExpression(GET_DETAILS_VIEW_HELP_TEXT_SERVICE, E_STRUCTURAL_FEATURE));
radio.setCandidatesExpression(AQLUtils.getSelfServiceCallExpression("getEnumCandidates", E_STRUCTURAL_FEATURE));
radio.setCandidateLabelExpression("aql:candidate.name");
radio.setValueExpression(AQLUtils.getSelfServiceCallExpression("getEnumValue", E_STRUCTURAL_FEATURE));
Expand All @@ -606,6 +617,7 @@ private WidgetDescription createReferenceWidget() {
ReferenceWidgetDescription refWidget = ReferenceFactory.eINSTANCE.createReferenceWidgetDescription();
refWidget.setName("ReferenceWidget");
refWidget.setLabelExpression(AQLUtils.getSelfServiceCallExpression(GET_DETAILS_VIEW_LABEL_SERVICE, E_STRUCTURAL_FEATURE));
refWidget.setHelpExpression(AQLUtils.getSelfServiceCallExpression(GET_DETAILS_VIEW_HELP_TEXT_SERVICE, E_STRUCTURAL_FEATURE));
refWidget.setReferenceNameExpression(AQLConstants.AQL + E_STRUCTURAL_FEATURE + ".name");
refWidget.setReferenceOwnerExpression(AQLConstants.AQL_SELF);
refWidget.setIsEnabledExpression(AQL_NOT_SELF_IS_READ_ONLY_E_STRUCTURAL_FEATURE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.eclipse.sirius.components.core.api.IReadOnlyObjectPredicate;
import org.eclipse.sirius.components.representations.Message;
import org.eclipse.sirius.components.representations.MessageLevel;
import org.eclipse.syson.form.services.api.IDetailsViewHelpTextProvider;
import org.eclipse.syson.application.configuration.SysMLv2PropertiesConfigurer;
import org.eclipse.syson.services.ElementInitializerSwitch;
import org.eclipse.syson.services.ImportService;
Expand Down Expand Up @@ -83,20 +84,23 @@ public class DetailsViewService {

private final IFeedbackMessageService feedbackMessageService;

private final IReadOnlyObjectPredicate readOnlyObjectPredicate;

private final List<IDetailsViewHelpTextProvider> detailsViewHelpTextProviders;

private final ImportService importService;

private final ElementInitializerSwitch elementInitializerSwitch;

private final EEnumLiteral unsetEnumLiteral;

private final IReadOnlyObjectPredicate readOnlyObjectPredicate;

private final UtilService utilService;

public DetailsViewService(List<Descriptor> composedAdapterFactoryDescriptors, IFeedbackMessageService feedbackMessageService, IReadOnlyObjectPredicate readOnlyObjectPredicate) {
public DetailsViewService(List<Descriptor> composedAdapterFactoryDescriptors, IFeedbackMessageService feedbackMessageService, IReadOnlyObjectPredicate readOnlyObjectPredicate, List<IDetailsViewHelpTextProvider> detailsViewHelpTextProviders) {
this.composedAdapterFactoryDescriptors = Objects.requireNonNull(composedAdapterFactoryDescriptors);
this.feedbackMessageService = Objects.requireNonNull(feedbackMessageService);
this.readOnlyObjectPredicate = Objects.requireNonNull(readOnlyObjectPredicate);
this.detailsViewHelpTextProviders = Objects.requireNonNull(detailsViewHelpTextProviders);
this.importService = new ImportService();
this.elementInitializerSwitch = new ElementInitializerSwitch();
this.unsetEnumLiteral = EcoreFactory.eINSTANCE.createEEnumLiteral();
Expand All @@ -109,6 +113,14 @@ public String getDetailsViewLabel(Element element, EStructuralFeature eStructura
return this.getLabelProvider().apply(element, eStructuralFeature);
}

public String getDetailsViewHelpText(Element element, EStructuralFeature eStructuralFeature) {
return this.detailsViewHelpTextProviders.stream()
.filter(provider -> provider.canHandle(element, eStructuralFeature))
.findFirst()
.map(provider -> provider.getHelpText(element, eStructuralFeature))
.orElse("");
}

public List<EStructuralFeature> getAdvancedFeatures(Element element) {
List<EStructuralFeature> coreFeatures = new CoreFeaturesSwitch().doSwitch(element);
return element.eClass().getEAllStructuralFeatures().stream().filter(feature -> !coreFeatures.contains(feature)).toList();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*******************************************************************************
* Copyright (c) 2025 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
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.syson.application.services.form;

import java.util.Objects;

import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.syson.form.services.api.IDetailsViewHelpTextProvider;
import org.eclipse.syson.sysml.Element;
import org.eclipse.syson.sysml.SysmlPackage;
import org.springframework.stereotype.Service;

/**
* Provides custom Help text for the Details view widgets of the features isComposite and isReference.
*
* @author mcharfadi
*/
@Service
public class IsCompositeIsReferenceDetailViewHelpTextProvider implements IDetailsViewHelpTextProvider {
@Override
public boolean canHandle(Element element, EStructuralFeature eStructuralFeature) {
return Objects.equals(eStructuralFeature, SysmlPackage.eINSTANCE.getFeature_IsComposite()) ||
Objects.equals(eStructuralFeature, SysmlPackage.eINSTANCE.getUsage_IsReference());
}

@Override
public String getHelpText(Element element, EStructuralFeature eStructuralFeature) {
var helpText = "";
if (Objects.equals(eStructuralFeature, SysmlPackage.eINSTANCE.getFeature_IsComposite())) {
helpText = "Opposite value of isReference";
} else if (Objects.equals(eStructuralFeature, SysmlPackage.eINSTANCE.getUsage_IsReference())) {
helpText = "Opposite value of isComposite (cannot be edited, edit isComposite instead)";
}
return helpText;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public void setUp() {
// SysON for the moment.
List<Descriptor> composedAdapterFactoryDescriptors = List.of();
this.detailsViewService = new DetailsViewService(composedAdapterFactoryDescriptors, new IFeedbackMessageService.NoOp(),
new ComposedReadOnlyObjectPredicate(List.of(new SysONReadOnlyObjectPredicateDelegate()), new DefaultReadOnlyObjectPredicate()));
new ComposedReadOnlyObjectPredicate(List.of(new SysONReadOnlyObjectPredicateDelegate()), new DefaultReadOnlyObjectPredicate()), List.of());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,20 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.sirius.components.forms.tests.FormEventPayloadConsumer.assertRefreshedFormThat;

import com.jayway.jsonpath.JsonPath;

import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;

import org.eclipse.sirius.components.collaborative.forms.dto.FormRefreshedEventPayload;
import org.eclipse.sirius.components.forms.Checkbox;
import org.eclipse.sirius.components.forms.LabelWidget;
import org.eclipse.sirius.components.forms.Textfield;
import org.eclipse.sirius.components.forms.tests.graphql.HelpTextQueryRunner;
import org.eclipse.sirius.components.forms.tests.navigation.FormNavigator;
import org.eclipse.sirius.web.application.views.details.dto.DetailsEventInput;
import org.eclipse.sirius.web.tests.graphql.DetailsEventSubscriptionRunner;
Expand Down Expand Up @@ -56,6 +62,9 @@ public class DetailsViewControllerIntegrationTests extends AbstractIntegrationTe
@Autowired
private DetailsEventSubscriptionRunner detailsEventSubscriptionRunner;

@Autowired
private HelpTextQueryRunner helpTextQueryRunner;

@Autowired
private RepresentationIdBuilder representationIdBuilder;

Expand Down Expand Up @@ -96,4 +105,71 @@ public void givenAPartUsageWhenWeSubscribeToItsPropertiesEventThenTheFormIsSent(

}

@Test
@DisplayName("GIVEN a PartUsage, WHEN we request the help text, THEN the help text is send")
@Sql(scripts = { SimpleProjectElementsTestProjectData.SCRIPT_PATH }, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
@Sql(scripts = { "/scripts/cleanup.sql" }, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
public void givenAPartUsageWhenWeRequestHelpTextThenHelpTextIsSend() {
var detailsRepresentationId = this.representationIdBuilder.buildDetailsRepresentationId(List.of(SimpleProjectElementsTestProjectData.SemanticIds.PART_ID));
var input = new DetailsEventInput(UUID.randomUUID(), SimpleProjectElementsTestProjectData.EDITING_CONTEXT_ID, detailsRepresentationId);
var flux = this.detailsEventSubscriptionRunner.run(input).filter(FormRefreshedEventPayload.class::isInstance);

var isCompositeWidgetId = new AtomicReference<String>();
var isReferenceWidgetId = new AtomicReference<String>();
var isPortionWidgetId = new AtomicReference<String>();
Consumer<Object> formContentConsumer = assertRefreshedFormThat(form -> {
var isCompositeWidget = new FormNavigator(form).page("Advanced").group("Part Properties").findWidget("Is Composite", Checkbox.class);
var isReferenceWidget = new FormNavigator(form).page("Advanced").group("Part Properties").findWidget("Is Reference", Checkbox.class);
var isPortionWidget = new FormNavigator(form).page("Advanced").group("Part Properties").findWidget("Is Portion", Checkbox.class);
assertThat(isCompositeWidget).isNotNull();
assertThat(isReferenceWidget).isNotNull();
isCompositeWidgetId.set(isCompositeWidget.getId());
isReferenceWidgetId.set(isReferenceWidget.getId());
isPortionWidgetId.set(isPortionWidget.getId());
});

Runnable requestIsCompositeHelpText = () -> {
Map<String, Object> variables = Map.of(
"editingContextId", SimpleProjectElementsTestProjectData.EDITING_CONTEXT_ID,
"representationId", detailsRepresentationId,
"widgetId", isCompositeWidgetId.get()
);
var result = this.helpTextQueryRunner.run(variables);

String helpText = JsonPath.read(result, "$.data.viewer.editingContext.representation.description.helpText");
assertThat(helpText).isEqualTo("Opposite value of isReference");
};

Runnable requestIsReferenceHelpText = () -> {
Map<String, Object> variables = Map.of(
"editingContextId", SimpleProjectElementsTestProjectData.EDITING_CONTEXT_ID,
"representationId", detailsRepresentationId,
"widgetId", isReferenceWidgetId.get()
);
var result = this.helpTextQueryRunner.run(variables);

String helpText = JsonPath.read(result, "$.data.viewer.editingContext.representation.description.helpText");
assertThat(helpText).isEqualTo("Opposite value of isComposite (cannot be edited, edit isComposite instead)");
};
Runnable requestIsPortionHelpText = () -> {
Map<String, Object> variables = Map.of(
"editingContextId", SimpleProjectElementsTestProjectData.EDITING_CONTEXT_ID,
"representationId", detailsRepresentationId,
"widgetId", isPortionWidgetId.get()
);
var result = this.helpTextQueryRunner.run(variables);

String helpText = JsonPath.read(result, "$.data.viewer.editingContext.representation.description.helpText");
assertThat(helpText).isEmpty();
};

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useless empty line

StepVerifier.create(flux)
.consumeNextWith(formContentConsumer)
.then(requestIsCompositeHelpText)
.then(requestIsReferenceHelpText)
.then(requestIsPortionHelpText)
.thenCancel()
.verify(Duration.ofSeconds(10));
}
}
Loading
Loading