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
2 changes: 1 addition & 1 deletion CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ This allows to limit the size of the indices, and ensure information stored in t
You can find more information on how to setup Elasticsearch, how elements are mapped to index documents, and how to query them in the documentation.
- https://github.com/eclipse-syson/syson/issues/1861[#1861] [publication] Split `SysONLibraryPublicationHandler` in two distinct classes so the publishing logic can be extended or re-used through the `ISysMLLibraryPublisher` API.
- https://github.com/eclipse-syson/syson/issues/1895[#1895] [export] Implement textual export of `StateUsage` and `StateDefinition`.

- https://github.com/eclipse-syson/syson/issues/1870[#1870] [diagrams] When dragging some `Element` into a diagram, hide its graphical node compartments unless it's an _interconnection_ compartment

=== New features

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*******************************************************************************
* Copyright (c) 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
* 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.controllers.diagrams.general.view;

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.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;

import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramEventInput;
import org.eclipse.sirius.components.collaborative.diagrams.dto.DropOnDiagramInput;
import org.eclipse.sirius.components.collaborative.diagrams.dto.DropOnDiagramSuccessPayload;
import org.eclipse.sirius.components.collaborative.diagrams.dto.InvokeSingleClickOnDiagramElementToolInput;
import org.eclipse.sirius.components.collaborative.diagrams.dto.InvokeSingleClickOnDiagramElementToolSuccessPayload;
import org.eclipse.sirius.components.diagrams.ViewModifier;
import org.eclipse.sirius.components.diagrams.tests.graphql.DropOnDiagramMutationRunner;
import org.eclipse.sirius.components.diagrams.tests.graphql.InvokeSingleClickOnDiagramElementToolMutationRunner;
import org.eclipse.sirius.components.diagrams.tests.graphql.PaletteQueryRunner;
import org.eclipse.sirius.components.diagrams.tests.navigation.DiagramNavigator;
import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState;
import org.eclipse.syson.AbstractIntegrationTests;
import org.eclipse.syson.SysONTestsProperties;
import org.eclipse.syson.application.data.GeneralViewItemAndAttributeProjectData;
import org.eclipse.syson.services.diagrams.api.IGivenDiagramSubscription;
import org.eclipse.syson.sysml.helper.LabelConstants;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.transaction.annotation.Transactional;

import reactor.test.StepVerifier;

/**
* Tests the visibility of dropped elements from the explorer on the General View diagram.
*
* @author mcharfadi
*/
@Transactional
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = { SysONTestsProperties.NO_DEFAULT_LIBRARIES_PROPERTY })
public class GVDropFromExplorerVisibilityTests extends AbstractIntegrationTests {

@Autowired
private IGivenInitialServerState givenInitialServerState;

@Autowired
private IGivenDiagramSubscription givenDiagramSubscription;

@Autowired
private DropOnDiagramMutationRunner dropOnDiagramMutationRunner;

@Autowired
private PaletteQueryRunner paletteQueryRunner;

@Autowired
private InvokeSingleClickOnDiagramElementToolMutationRunner invokeSingleClickOnDiagramElementToolMutationRunner;

@DisplayName("GIVEN a diagram, WHEN we drop a PartUsage with no empty compartments from the Explorer view, THEN the PartUsage is displayed on the diagram with its compartments hidden")
@Sql(scripts = { GeneralViewItemAndAttributeProjectData.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))
@Test
public void dropPartFromTheExplorer() {
this.givenInitialServerState.initialize();
var diagramEventInput = new DiagramEventInput(UUID.randomUUID(),
GeneralViewItemAndAttributeProjectData.EDITING_CONTEXT_ID,
GeneralViewItemAndAttributeProjectData.GraphicalIds.DIAGRAM_ID);

var flux = this.givenDiagramSubscription.subscribe(diagramEventInput);

var diagramId = new AtomicReference<String>();
var removeFromDiagramToolId = new AtomicReference<String>();
var diagramTargetId = new AtomicReference<String>();
var partNodeId = new AtomicReference<String>();
var partNodeSemanticId = new AtomicReference<String>();

Consumer<Object> diagramContentConsumerBeforeDrop = assertRefreshedDiagramThat(diagram -> {
assertThat(diagram.getNodes()).hasSize(3);
diagramTargetId.set(diagram.getTargetObjectId());
diagramId.set(diagram.getId());
var partNode = new DiagramNavigator(diagram).nodeWithLabel(LabelConstants.OPEN_QUOTE + "part" + LabelConstants.CLOSE_QUOTE + LabelConstants.CR + "p1").getNode();
assertThat(partNode.getChildNodes().stream().filter(node -> node.getModifiers().contains(ViewModifier.Hidden))).hasSize(9);
partNodeSemanticId.set(partNode.getTargetObjectId());
partNodeId.set(partNode.getId());
});

Runnable getRemoveFromDiagramTool = () -> {
Map<String, Object> variables = Map.of(
"editingContextId", GeneralViewItemAndAttributeProjectData.EDITING_CONTEXT_ID,
"representationId", diagramId.get(),
"diagramElementIds", List.of(partNodeId.get())
);
var result = this.paletteQueryRunner.run(variables);

List<String> labels = JsonPath.read(result.data(), "$.data.viewer.editingContext.representation.description.palette.quickAccessTools[*].label");
assertThat(labels).hasSize(8);
assertThat(labels.get(6)).isEqualTo("Delete from Diagram");
List<String> ids = JsonPath.read(result.data(), "$.data.viewer.editingContext.representation.description.palette.quickAccessTools[*].id");
removeFromDiagramToolId.set(ids.get(6));
};

// Remove the node from the diagram
Runnable executeRemoveFromDiagramTool = () -> {
var input = new InvokeSingleClickOnDiagramElementToolInput(UUID.randomUUID(), GeneralViewItemAndAttributeProjectData.EDITING_CONTEXT_ID, diagramId.get(), List.of(partNodeId.get()), removeFromDiagramToolId.get(), 0, 0, List.of());
var result = this.invokeSingleClickOnDiagramElementToolMutationRunner.run(input);
String typename = JsonPath.read(result.data(), "$.data.invokeSingleClickOnDiagramElementTool.__typename");
assertThat(typename).isEqualTo(InvokeSingleClickOnDiagramElementToolSuccessPayload.class.getSimpleName());
};

Consumer<Object> diagramContentConsumerAfterRemove = assertRefreshedDiagramThat(diagram -> {
assertThat(diagram.getNodes()).hasSize(2);
});

// Drop from the explorer
Runnable executeDropPartOnDiagram = () -> {
var dropOnDiagramInput = new DropOnDiagramInput(UUID.randomUUID(), GeneralViewItemAndAttributeProjectData.EDITING_CONTEXT_ID, diagramId.get(),
diagramTargetId.get(), List.of(partNodeSemanticId.get()), 0, 0);
var dropOnDiagramResult = this.dropOnDiagramMutationRunner.run(dropOnDiagramInput);
var typename = JsonPath.read(dropOnDiagramResult.data(), "$.data.dropOnDiagram.__typename");
assertThat(typename).isEqualTo(DropOnDiagramSuccessPayload.class.getSimpleName());
};

Consumer<Object> diagramContentConsumerAfterDrop = assertRefreshedDiagramThat(diagram -> {
assertThat(diagram.getNodes()).hasSize(3);
diagramTargetId.set(diagram.getTargetObjectId());
diagramId.set(diagram.getId());
var partNode = new DiagramNavigator(diagram).nodeWithLabel(LabelConstants.OPEN_QUOTE + "part" + LabelConstants.CLOSE_QUOTE + LabelConstants.CR + "p1").getNode();
assertThat(partNode.getChildNodes().stream().filter(node -> node.getModifiers().contains(ViewModifier.Hidden))).hasSize(11);
partNodeSemanticId.set(partNode.getTargetObjectId());
partNodeId.set(partNode.getId());
});

StepVerifier.create(flux)
.consumeNextWith(diagramContentConsumerBeforeDrop)
.then(getRemoveFromDiagramTool)
.then(executeRemoveFromDiagramTool)
.consumeNextWith(diagramContentConsumerAfterRemove)
.then(executeDropPartOnDiagram)
.consumeNextWith(diagramContentConsumerAfterDrop)
.thenCancel()
.verify(Duration.ofSeconds(10));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*******************************************************************************
* Copyright (c) 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
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.syson.standard.diagrams.view.services;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import org.eclipse.sirius.components.collaborative.api.ChangeDescription;
import org.eclipse.sirius.components.collaborative.diagrams.api.IDiagramEventConsumer;
import org.eclipse.sirius.components.collaborative.diagrams.dto.DropOnDiagramInput;
import org.eclipse.sirius.components.core.api.IEditingContext;
import org.eclipse.sirius.components.core.api.IObjectSearchService;
import org.eclipse.sirius.components.diagrams.Diagram;
import org.eclipse.sirius.components.diagrams.ViewCreationRequest;
import org.eclipse.sirius.components.diagrams.ViewDeletionRequest;
import org.eclipse.sirius.components.diagrams.components.NodeContainmentKind;
import org.eclipse.sirius.components.diagrams.components.NodeIdProvider;
import org.eclipse.sirius.components.diagrams.description.NodeDescription;
import org.eclipse.sirius.components.diagrams.events.HideDiagramElementEvent;
import org.eclipse.sirius.components.diagrams.events.IDiagramEvent;
import org.eclipse.sirius.components.view.emf.diagram.ViewDiagramConversionData;
import org.eclipse.sirius.web.application.editingcontext.EditingContext;
import org.eclipse.syson.diagram.services.DiagramQueryElementService;
import org.eclipse.syson.standard.diagrams.view.SDVDescriptionNameGenerator;
import org.eclipse.syson.sysml.Element;
import org.eclipse.syson.util.IDescriptionNameGenerator;
import org.eclipse.syson.util.SysONRepresentationDescriptionIdentifiers;
import org.springframework.stereotype.Service;

/**
* Service that will be executed after a DropOnDiagramInput in order to hide compartments.
* If it's an interconnection compartment then we keep the default behavior of {@link org.eclipse.syson.diagram.common.view.services.ViewNodeService#isHiddenByDefault(Element, String)}
*
* @author mcharfadi
*/
@Service
public class DiagramMutationDropDefaultVisibility implements IDiagramEventConsumer {

private final DiagramQueryElementService diagramQueryElementService;

private final IObjectSearchService objectSearchService;

public DiagramMutationDropDefaultVisibility(DiagramQueryElementService diagramQueryElementService, IObjectSearchService objectSearchService) {
this.diagramQueryElementService = diagramQueryElementService;
this.objectSearchService = objectSearchService;
}

@Override
public void accept(IEditingContext editingContext, Diagram previousDiagram, List<IDiagramEvent> diagramEvents, List<ViewDeletionRequest> viewDeletionRequests, List<ViewCreationRequest> viewCreationRequests, ChangeDescription changeDescription) {
if (changeDescription.getInput() instanceof DropOnDiagramInput input && this.isStandardDiagram(previousDiagram) && editingContext instanceof EditingContext siriusEditingContext
&& siriusEditingContext.getViewConversionData().get(previousDiagram.getDescriptionId()) instanceof ViewDiagramConversionData viewDiagramConversionData) {

IDescriptionNameGenerator descriptionNameGenerator = new SDVDescriptionNameGenerator();
var interconnectionCompartmentName = descriptionNameGenerator.getFreeFormCompartmentName("interconnection");
var interConnectionCompartment = viewDiagramConversionData.convertedNodes().entrySet().stream()
.filter(nodeDescriptionNodeDescriptionEntry -> nodeDescriptionNodeDescriptionEntry.getKey().getName().equals(interconnectionCompartmentName))
.map(Map.Entry::getValue)
.map(NodeDescription::getId)
.findFirst();

Set<String> compartmentsToHide = new HashSet<>();
input.objectIds().forEach(objectId -> {
var semanticParent = this.objectSearchService.getObject(editingContext, objectId);
if (semanticParent.isPresent() && semanticParent.get() instanceof Element element) {
var optionalParentNodeDescriptionId = this.diagramQueryElementService.getNodeDescriptionId(element, previousDiagram, editingContext);
if (optionalParentNodeDescriptionId.isPresent()) {
var parentNodeId = new NodeIdProvider().getNodeId(previousDiagram.getId(),
optionalParentNodeDescriptionId.get(),
NodeContainmentKind.CHILD_NODE,
objectId);

var parentNodeDescription = viewDiagramConversionData.convertedNodes().values().stream()
.filter(nodeDescription -> nodeDescription.getId().equals(optionalParentNodeDescriptionId.get()))
.findFirst();

parentNodeDescription.ifPresent(nodeDescription -> nodeDescription.getReusedChildNodeDescriptionIds().stream()
.filter(reusedChildNodeDescriptionId -> interConnectionCompartment.isEmpty() || !interConnectionCompartment.get().equals(reusedChildNodeDescriptionId))
.forEach(reusedChildNodeDescriptionId -> {
var containerNodeToHideId = new NodeIdProvider().getNodeId(parentNodeId,
reusedChildNodeDescriptionId,
NodeContainmentKind.CHILD_NODE,
objectId);
compartmentsToHide.add(containerNodeToHideId);
}));

diagramEvents.add(new HideDiagramElementEvent(compartmentsToHide, true));
}
}
});
}

}

private boolean isStandardDiagram(Diagram diagram) {
return diagram != null && Objects.equals(SysONRepresentationDescriptionIdentifiers.GENERAL_VIEW_DIAGRAM_DESCRIPTION_ID, diagram.getDescriptionId());
}
}
19 changes: 14 additions & 5 deletions doc/content/modules/user-manual/pages/release-notes/2026.3.0.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,32 @@

== New features

- SysON can now be connected with Elasticsearch to support cross-project search.
* SysON can now be connected with Elasticsearch to support cross-project search.
The Elasticsearch integration uses its own indexing logic instead of the default one provided by Sirius Web.
This allows to keep indices compact, and ensures information stored in the indices are useful to perform cross-project search.
You can find more information on how to setup Elasticsearch, how elements are mapped to index documents, and how to query them in the documentation.

+
This feature is currently considered experimental.
Try it out and give feedback by reporting bugs and suggesting new features.
It's not recommended for production use.

== Bug fixes

* Fix the textual export of `OccurrenceUsage` to avoid duplication of the _abstract_ keyword.
* Fix the textual export to properly escape names used in qualified names in some references.
* In textual import/export:

** Fix the textual export of `OccurrenceUsage` to avoid duplication of the _abstract_ keyword.
** Fix the textual export to properly escape names used in qualified names in some references.

== Improvements

* Implement the textual export for `StateUsage` and `StateDefinition`.
* In diagrams:

** When dropping `Element` from the _Explorer_ view to a diagram, hide its compartments unless it's the _interconnection compartment_ on an _Interconnection View_ diagram.

* In textual import/export:

** Implement the textual export for `StateUsage` and `StateDefinition`.


== Technical details

Expand Down
Loading