Skip to content
Draft
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 @@ -46,6 +46,7 @@ This fix ensure that imported models containing, for example, a top-level `Libra
- https://github.com/eclipse-syson/syson/issues/1565[#1565] Provide a way to duplicate semantic element in the _Explorer_ view.
- https://github.com/eclipse-syson/syson/issues/1737[#1737] [diagrams] Add creation tools to InterconnectionCompartmentNode
- 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/1747[#1747] [diagrams] Most new elements created by invoking a tool on a diagram are now automatically selected

== v2025.12.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
*******************************************************************************/
package org.eclipse.syson.diagram.common.view.tools;

import java.util.List;

import org.eclipse.sirius.components.collaborative.diagrams.DiagramContext;
import org.eclipse.sirius.components.core.api.IEditingContext;
import org.eclipse.sirius.components.diagrams.Node;
Expand All @@ -25,9 +23,9 @@
import org.eclipse.sirius.components.view.diagram.NodeTool;
import org.eclipse.sirius.components.view.diagram.SelectionDialogDescription;
import org.eclipse.sirius.components.view.emf.diagram.ViewDiagramDescriptionConverter;
import org.eclipse.syson.diagram.common.view.services.ViewNodeService;
import org.eclipse.syson.diagram.services.aql.DiagramMutationAQLService;
import org.eclipse.syson.util.AQLConstants;
import org.eclipse.syson.util.AQLUtils;
import org.eclipse.syson.util.ServiceMethod;

/**
Expand All @@ -37,6 +35,8 @@
*/
public abstract class AbstractCompartmentNodeToolProvider implements INodeToolProvider {

private static final String NEW_INSTANCE = "newInstance";

protected final DiagramBuilders diagramBuilderHelper = new DiagramBuilders();

protected final ViewBuilders viewBuilderHelper = new ViewBuilders();
Expand Down Expand Up @@ -77,8 +77,8 @@ public NodeTool create(IViewDiagramElementFinder cache) {
ChangeContextBuilder revealOperation;
if (this.revealOnCreate()) {
revealOperation = this.viewBuilderHelper.newChangeContext()
.expression(AQLUtils.getServiceCallExpression(Node.SELECTED_NODE, "revealCompartment",
List.of("self", DiagramContext.DIAGRAM_CONTEXT, IEditingContext.EDITING_CONTEXT, ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE)));
.expression(ServiceMethod.of4(ViewNodeService::revealCompartment).aql(Node.SELECTED_NODE, AQLConstants.SELF, DiagramContext.DIAGRAM_CONTEXT, IEditingContext.EDITING_CONTEXT,
ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE));
} else {
revealOperation = this.viewBuilderHelper.newChangeContext().expression(AQLConstants.AQL_SELF);
}
Expand All @@ -87,19 +87,25 @@ public NodeTool create(IViewDiagramElementFinder cache) {
.expression(ServiceMethod.of4(DiagramMutationAQLService::expose).aqlSelf(IEditingContext.EDITING_CONTEXT, DiagramContext.DIAGRAM_CONTEXT, Node.SELECTED_NODE,
ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE));

var creationCompartmentItemServiceCall = this.viewBuilderHelper.newChangeContext()
.expression(this.getServiceCallExpression())
var exposeAndRevealNewInstance = this.viewBuilderHelper.newChangeContext()
.expression(AQLConstants.AQL + NEW_INSTANCE)
.children(addToExposedElements.build(), revealOperation.build());

var letNewInstance = this.viewBuilderHelper.newLet()
.variableName(NEW_INSTANCE)
.valueExpression(this.getServiceCallExpression())
.children(exposeAndRevealNewInstance.build());

var rootChangContext = this.viewBuilderHelper.newChangeContext()
.expression(AQLConstants.AQL_SELF)
.children(creationCompartmentItemServiceCall.build())
.children(letNewInstance.build())
.build();

return builder.name(this.getNodeToolName())
.iconURLsExpression(this.getNodeToolIconURLsExpression())
.body(rootChangContext)
.preconditionExpression(this.getPreconditionExpression())
.elementsToSelectExpression(AQLConstants.AQL + NEW_INSTANCE)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
*******************************************************************************/
package org.eclipse.syson.diagram.common.view.tools;

import java.util.List;

import org.eclipse.sirius.components.collaborative.diagrams.DiagramContext;
import org.eclipse.sirius.components.core.api.IEditingContext;
import org.eclipse.sirius.components.diagrams.Node;
Expand All @@ -23,8 +21,10 @@
import org.eclipse.sirius.components.view.builder.providers.INodeToolProvider;
import org.eclipse.sirius.components.view.diagram.NodeTool;
import org.eclipse.sirius.components.view.emf.diagram.ViewDiagramDescriptionConverter;
import org.eclipse.syson.diagram.common.view.services.ViewCreateService;
import org.eclipse.syson.diagram.common.view.services.ViewNodeService;
import org.eclipse.syson.diagram.services.aql.DiagramMutationAQLService;
import org.eclipse.syson.util.AQLUtils;
import org.eclipse.syson.util.AQLConstants;
import org.eclipse.syson.util.ServiceMethod;

/**
Expand All @@ -34,6 +34,8 @@
*/
public class ActionFlowCompartmentNodeToolProvider implements INodeToolProvider {

private static final String NEW_INSTANCE = "newInstance";

private final DiagramBuilders diagramBuilderHelper = new DiagramBuilders();

private final ViewBuilders viewBuilderHelper = new ViewBuilders();
Expand All @@ -47,18 +49,19 @@ public NodeTool create(IViewDiagramElementFinder cache) {
ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE));

var revealOperation = this.viewBuilderHelper.newChangeContext()
.expression(
AQLUtils.getServiceCallExpression(Node.SELECTED_NODE, "revealCompartment",
List.of("self", DiagramContext.DIAGRAM_CONTEXT, IEditingContext.EDITING_CONTEXT, ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE)));
.expression(ServiceMethod.of4(ViewNodeService::revealCompartment).aql(Node.SELECTED_NODE, AQLConstants.SELF, DiagramContext.DIAGRAM_CONTEXT, IEditingContext.EDITING_CONTEXT,
ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE));

var creationServiceCall = this.viewBuilderHelper.newChangeContext()
.expression(AQLUtils.getSelfServiceCallExpression("createSubActionUsage"))
var letNewInstance = this.viewBuilderHelper.newLet()
.variableName(NEW_INSTANCE)
.valueExpression(ServiceMethod.of0(ViewCreateService::createSubActionUsage).aqlSelf())
.children(addToExposedElements.build(), revealOperation.build())
.build();

return builder.name("New Action")
.iconURLsExpression("/icons/full/obj16/ActionUsage.svg")
.body(creationServiceCall)
.body(letNewInstance)
.elementsToSelectExpression(AQLConstants.AQL + NEW_INSTANCE)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ This applies to: `FeatureTyping`, `Subsetting`, `Redefinition` and `Subclassific
image::release-notes-redundant-feature-typing-message.png[Message displayed when attempting to create a redundant feature typing]

- In diagrams, it is now possible to create a `SatisfyRequirement` from a `PartDefinition` or `PartUsage` graphical node.
- In diagrams, many new tools now automatically select the `Elements` they create or expose on a diagram.
This makes it easy to start editing newly created elements to give them proper names by starting to type directly when the new element appears.

== Technical details

Expand Down
114 changes: 114 additions & 0 deletions integration-tests-playwright/playwright/e2e/diagram-selection.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*******************************************************************************
* 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 { PlaywrightExplorer } from '../helpers/PlaywrightExplorer';
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');
projectId = project.projectId;

await page.goto(`/projects/${projectId}/edit`);
const playwrightExplorer = new PlaywrightExplorer(page);
await playwrightExplorer.uploadDocument('SysMLv2WithGeneralView.sysml');
await playwrightExplorer.expand('SysMLv2WithGeneralView.sysml');
await playwrightExplorer.expand('Package1');
await playwrightExplorer.createRepresentation('view1 [GeneralView]', 'General View', 'view1');

// Make sure all the elements are visible and at well-defined locations
await page.getByTestId('arrange-all-menu').click();
await page.getByTestId('arrange-all-elk-layered').click();
await page.getByTestId('zoom-out').click();
});

test.afterEach(async ({ request }) => {
await new PlaywrightProject(request).deleteProject(projectId);
});

test('WHEN creating an attribute inside a part, THEN the new attribute is automatically selected and can be renamed immediately', async ({
page,
}) => {
const partNode = new PlaywrightNode(page, 'part1', 'List');
await expect(partNode.nodeLocator).toBeAttached();

// Create a new attribute
await partNode.click();
await partNode.openPalette();
await page.getByTestId('toolSection-Structure').click();
await page.getByTestId('tool-New Attribute').click();

// It should be selected
const newNode = new PlaywrightNode(page, 'attribute1', 'IconLabel');
await expect(newNode.nodeLocator).toBeAttached();
await expect(newNode.nodeLocator).toContainClass('selected');

// Start typing a new name immediately
const newName = 'editedAttribute';
await page.keyboard.type(newName);
await page.keyboard.press('Enter');
await expect(newNode.nodeLocator).not.toBeAttached();

// The newly created node has changed its label and is still selected
const editedNode = new PlaywrightNode(page, newName, 'IconLabel');
await expect(editedNode.nodeLocator).toBeAttached();
await expect(editedNode.nodeLocator).toContainClass('selected');

// The selection can be seen from the rest of the workbench, e.g. in the Explorer
const explorer = new PlaywrightExplorer(page);
const treeItem = await explorer.getTreeItemLabel(newName);
await expect(treeItem).not.toBeVisible();
await explorer.revealGlobalSelection();
await expect(treeItem).toBeVisible();
});

test('WHEN creating a port on a part, THEN the new port is automatically selected', async ({ page }) => {
const partNode = new PlaywrightNode(page, 'part1', 'List');
await expect(partNode.nodeLocator).toBeAttached();

// Create a new port
await partNode.click();
await partNode.openPalette();
await page.getByTestId('toolSection-Structure').click();
await page.getByTestId('tool-New Port').click();

// It should be selected
const newNode = new PlaywrightNode(page, 'port1', 'FreeForm');
await expect(newNode.nodeLocator).toBeAttached();
await expect(newNode.nodeLocator).toContainClass('selected');

// Start typing a new name immediately
const newName = 'editedPort';
await page.keyboard.type(newName);
await page.keyboard.press('Enter');
await expect(newNode.nodeLocator).not.toBeAttached();

// The newly created node has changed its label and is still selected
const editedNode = new PlaywrightNode(page, newName, 'FreeForm');
await expect(editedNode.nodeLocator).toBeAttached();
await expect(editedNode.nodeLocator).toContainClass('selected');

// The selection can be seen from the rest of the workbench, e.g. in the Explorer
const explorer = new PlaywrightExplorer(page);
const treeItem = await explorer.getTreeItemLabel(newName);
await expect(treeItem).not.toBeVisible();
await explorer.revealGlobalSelection();
await expect(treeItem).toBeVisible();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package Package1 {
view view1 : StandardViewDefinitions::GeneralView {
expose part1;
}
part part1;
}
Loading