From 75815479de394b5956cf0b1fe1d02f36910bd751 Mon Sep 17 00:00:00 2001 From: Michael Charfadi Date: Wed, 17 Dec 2025 15:54:35 +0100 Subject: [PATCH] [1752] Hide top node if semantic element is created from a compartment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: https://github.com/eclipse-syson/syson/issues/1752 Signed-off-by: Michaƫl Charfadi --- CHANGELOG.adoc | 1 + .../DiagramMutationExposeService.java | 15 ++- .../services/api/SiriusWebCoreServices.java | 4 +- .../pages/release-notes/2026.1.0.adoc | 2 +- .../e2e/diagram_node_creation.spec.ts | 96 +++++++++++++++++++ .../playwright/helpers/PlaywrightDiagram.ts | 43 +++++++++ .../playwright/helpers/PlaywrightNode.ts | 9 ++ 7 files changed, 166 insertions(+), 4 deletions(-) create mode 100644 integration-tests-playwright/playwright/e2e/diagram_node_creation.spec.ts create mode 100644 integration-tests-playwright/playwright/helpers/PlaywrightDiagram.ts diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 997eb626b..c3a9d4873 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -43,6 +43,7 @@ This fix ensure that imported models containing, for example, a top-level `Libra - https://github.com/eclipse-syson/syson/issues/1765[#1765] [diagrams] Allow to create a `SatisfyRequirement` on `PartDefinition` or `PartUsage`. A new compartment named _satisfy requirements_ has also been added to `PartDefinition` and `PartUsage` graphical nodes in diagrams. - https://github.com/eclipse-syson/syson/issues/1762[#1762] [diagrams] Increase the default size of nodes. +- https://github.com/eclipse-syson/syson/issues/1752[#1752] [diagrams] In diagrams, hide the node displayed on the top of the diagram if the candidate compartment is not hidden. === New features diff --git a/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramMutationExposeService.java b/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramMutationExposeService.java index 7f38f6cad..8755d40a7 100644 --- a/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramMutationExposeService.java +++ b/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramMutationExposeService.java @@ -347,10 +347,21 @@ private void deleteExpose(List exposed, Object exposedElement) { private void hideNodeIfVisibleCompartmentCouldHostTheFutureNode(Element element, IEditingContext editingContext, DiagramContext diagramContext, Node selectedNode, Map convertedNodes) { + // If the creation tool was executed from a compartment, we're looking for sibling from the parent node of the compartment + // We're comparing the targetObjectId of the selected node and its parent in order to know if we're on the same semantic element + var referenceParent = selectedNode; + var selectedNodeTargetObjectId = selectedNode.getTargetObjectId(); + var nullableParentNode = new NodeFinder(diagramContext.diagram()).getParent(selectedNode); + if (nullableParentNode instanceof Node parentNode) { + var parentTargetObjectId = parentNode.getTargetObjectId(); + if (parentTargetObjectId.equals(selectedNodeTargetObjectId)) { + referenceParent = parentNode; + } + } // 1- get visible compartments of selected node // 2- get visible compartments that could host the future node // 3- if a visible compartment could host the future node, then hide the tree node - List visibleCompartments = selectedNode.getChildNodes().stream().filter(n -> n.getState().equals(ViewModifier.Normal)).toList(); + List visibleCompartments = referenceParent.getChildNodes().stream().filter(n -> n.getState().equals(ViewModifier.Normal)).toList(); boolean visibleCompartmentsThatCouldHostTheFutureNode = false; for (Node visibleCompartment : visibleCompartments) { Optional childNodeDescriptionIdForRendering = this.diagramMutationElementService.getChildNodeDescriptionIdForRendering(element, editingContext, diagramContext, visibleCompartment, @@ -361,7 +372,7 @@ private void hideNodeIfVisibleCompartmentCouldHostTheFutureNode(Element element, } } if (visibleCompartmentsThatCouldHostTheFutureNode) { - var parentId = this.diagramQueryElementService.getGraphicalParentId(diagramContext, selectedNode); + var parentId = this.diagramQueryElementService.getGraphicalParentId(diagramContext, referenceParent); var descriptionId = this.diagramQueryElementService.getNodeDescriptionId(element, diagramContext.diagram(), editingContext); if (parentId != null && descriptionId.isPresent()) { var nodeId = new NodeIdProvider().getNodeId(parentId, diff --git a/backend/services/syson-services/src/main/java/org/eclipse/syson/services/api/SiriusWebCoreServices.java b/backend/services/syson-services/src/main/java/org/eclipse/syson/services/api/SiriusWebCoreServices.java index b80989215..b023f126f 100644 --- a/backend/services/syson-services/src/main/java/org/eclipse/syson/services/api/SiriusWebCoreServices.java +++ b/backend/services/syson-services/src/main/java/org/eclipse/syson/services/api/SiriusWebCoreServices.java @@ -19,6 +19,7 @@ import org.eclipse.sirius.components.core.api.IIdentityService; import org.eclipse.sirius.components.core.api.IObjectSearchService; import org.eclipse.sirius.components.core.api.IRepresentationDescriptionSearchService; +import org.eclipse.sirius.components.core.api.IURLParser; import org.springframework.stereotype.Service; /** @@ -28,7 +29,7 @@ */ @Service public record SiriusWebCoreServices(IObjectSearchService objectSearchService, IIdentityService identityService, IFeedbackMessageService feedbackMessageService, - IEditingContextSearchService editingContextSearchService, IRepresentationDescriptionSearchService representationDescriptionSearchService) { + IEditingContextSearchService editingContextSearchService, IRepresentationDescriptionSearchService representationDescriptionSearchService, IURLParser urlParser) { public SiriusWebCoreServices { Objects.requireNonNull(objectSearchService); @@ -36,5 +37,6 @@ public record SiriusWebCoreServices(IObjectSearchService objectSearchService, II Objects.requireNonNull(feedbackMessageService); Objects.requireNonNull(editingContextSearchService); Objects.requireNonNull(representationDescriptionSearchService); + Objects.requireNonNull(urlParser); } } diff --git a/doc/content/modules/user-manual/pages/release-notes/2026.1.0.adoc b/doc/content/modules/user-manual/pages/release-notes/2026.1.0.adoc index 2b2187bf6..ab26b4d3c 100644 --- a/doc/content/modules/user-manual/pages/release-notes/2026.1.0.adoc +++ b/doc/content/modules/user-manual/pages/release-notes/2026.1.0.adoc @@ -78,7 +78,7 @@ a|image::release-notes-enumeration-definition-after.png[Enumeration definition n - In diagrams, an _items_ compartment is now available on `PortDefinition` graphical nodes to display `ItemUsage`. - In diagrams, an `ItemUsage` graphical border node is now available on `PortDefinition` graphical nodes. - +- In diagrams, when creating an element from a compartment, it will not be displayed by default on the diagram, but only in the compartment. == Technical details diff --git a/integration-tests-playwright/playwright/e2e/diagram_node_creation.spec.ts b/integration-tests-playwright/playwright/e2e/diagram_node_creation.spec.ts new file mode 100644 index 000000000..de5ee75b5 --- /dev/null +++ b/integration-tests-playwright/playwright/e2e/diagram_node_creation.spec.ts @@ -0,0 +1,96 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +import { expect, test } from '@playwright/test'; +import { PlaywrightDiagram } from '../helpers/PlaywrightDiagram'; +import { PlaywrightNode } from '../helpers/PlaywrightNode'; +import { PlaywrightProject } from '../helpers/PlaywrightProject'; + +test.describe('diagram - general view', () => { + let projectId; + test.beforeEach(async ({ page, request }) => { + await page.addInitScript(() => { + // @ts-expect-error: we use a variable in the DOM to disable `fitView` functionality for Cypress tests. + window.document.DEACTIVATE_FIT_VIEW_FOR_CYPRESS_TESTS = true; + }); + const project = await new PlaywrightProject(request).createSysMLV2Project('general view'); + projectId = project.projectId; + await page.goto(`/projects/${projectId}/edit`); + await page.locator(`[data-testid="onboard-open-view1"]`).first().click(); + }); + + test.afterEach(async ({ request }) => { + await new PlaywrightProject(request).deleteProject(projectId); + }); + + test('when creating a port on a part definition, then the port top node is hidden', async ({ page }) => { + const diagram = new PlaywrightDiagram(page); + // Create PartDef + await page.getByTestId('rf__wrapper').click({ button: 'right' }); + await expect(page.getByTestId('Palette')).toBeAttached(); + await page.getByTestId('toolSection-Structure').click(); + await page.getByTestId('tool-New Part Definition').click(); + diagram.expectNumberOfTopNodes(1); + // Create Port + const partDefinitionNode = new PlaywrightNode(page, 'PartDefinition1', 'List'); + await expect(partDefinitionNode.nodeLocator).toBeAttached(); + await partDefinitionNode.openPalette(); + await page.getByTestId('toolSection-Structure').click(); + await page.getByTestId('tool-New Port Inout').click(); + // The port is only displayed as a border node of the parent + const portBorderNode = new PlaywrightNode(page, 'port1Inout'); + await expect(portBorderNode.nodeLocator).toBeAttached(); + diagram.expectNumberOfTopNodes(2); + // The port can be displayed as a top node + await diagram.revealElement('port1Inout'); + const portNode = new PlaywrightNode(page, 'port1Inout', 'List'); + await expect(portNode.nodeLocator).toBeAttached(); + diagram.expectNumberOfTopNodes(3); + // The port can be displayed as a list item + await partDefinitionNode.revealElement('ports'); + const portsListNode = new PlaywrightNode(page, 'ports', 'List'); + await expect(portsListNode.nodeLocator).toBeAttached(); + const portListItemNode = new PlaywrightNode(page, 'inout port1Inout', 'IconLabel'); + await expect(portListItemNode.nodeLocator).toBeAttached(); + await diagram.expectNumberOfTopNodes(4); + }); + + test('when creating a port on a port compartment of a part definition, then the port top node is hidden', async ({ + page, + }) => { + const diagram = new PlaywrightDiagram(page); + // Create PartDef + await page.getByTestId('rf__wrapper').click({ button: 'right' }); + await expect(page.getByTestId('Palette')).toBeAttached(); + await page.getByTestId('toolSection-Structure').click(); + await page.getByTestId('tool-New Part Definition').click(); + diagram.expectNumberOfTopNodes(1); + const partDefinitionNode = new PlaywrightNode(page, 'PartDefinition1', 'List'); + await expect(partDefinitionNode.nodeLocator).toBeAttached(); + // Show the port compartment + await partDefinitionNode.revealElement('ports'); + const portsListNode = new PlaywrightNode(page, 'ports', 'List'); + await expect(portsListNode.nodeLocator).toBeAttached(); + await portsListNode.openPalette(); + // Create Port + await diagram.expectNumberOfTopNodes(2); + await page.getByTestId('toolSection-Structure').click(); + await page.getByTestId('tool-New Port Inout').click(); + // The port is displayed as an item node + const portListItemNode = new PlaywrightNode(page, 'inout port1Inout', 'IconLabel'); + await expect(portListItemNode.nodeLocator).toBeAttached(); + // The port is displayed as a border node of the parent + const portBorderNode = new PlaywrightNode(page, 'port1Inout'); + await expect(portBorderNode.nodeLocator).toBeAttached(); + diagram.expectNumberOfTopNodes(4); + }); +}); diff --git a/integration-tests-playwright/playwright/helpers/PlaywrightDiagram.ts b/integration-tests-playwright/playwright/helpers/PlaywrightDiagram.ts new file mode 100644 index 000000000..a5e6657e3 --- /dev/null +++ b/integration-tests-playwright/playwright/helpers/PlaywrightDiagram.ts @@ -0,0 +1,43 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +import { expect, type Page } from '@playwright/test'; + +export class PlaywrightDiagram { + readonly page: Page; + + constructor(page: Page) { + this.page = page; + } + + async expectNumberOfTopNodes(expectedNumberOfTopNodes: number) { + expect(await this.page.locator('.react-flow__nodes > div').count()).toBe(expectedNumberOfTopNodes); + } + + async revealElement(name: string) { + await this.page.getByTestId('filter-elements').click(); + const popup = this.page.getByTestId('group-Filter elements'); + await expect(popup).toBeAttached(); + await popup.locator(`[data-testid$="${name}"]`).click(); + //The drop down lack a proper data-testid so we use its sibling + await expect(popup.locator(`[data-testid="Apply to 1 selected element:"]`)).toBeAttached(); + await popup + .locator(`[data-testid="Apply to 1 selected element:"]`) + .locator('..') + .locator('> button') + .last() + .click(); + await this.page.getByAltText('Show').click(); + await popup.locator(`[data-testid="Apply to 1 selected element:"]`).click(); + await this.page.keyboard.press('Escape'); + } +} diff --git a/integration-tests-playwright/playwright/helpers/PlaywrightNode.ts b/integration-tests-playwright/playwright/helpers/PlaywrightNode.ts index 7545ab929..3814cd7d0 100644 --- a/integration-tests-playwright/playwright/helpers/PlaywrightNode.ts +++ b/integration-tests-playwright/playwright/helpers/PlaywrightNode.ts @@ -125,4 +125,13 @@ export class PlaywrightNode { // see https://playwright.dev/docs/actionability await this.nodeLocator.click(); } + + async revealElement(name: string) { + await this.nodeLocator.hover({ position: { x: 10, y: 10 } }); + await this.page + .getByTestId('manage-visibility') + .locator('> svg') + .click({ position: { x: 1, y: 1 } }); + await this.page.getByTestId(`manage_visibility_list_item_button_${name}`).click(); + } }