Skip to content

Commit

Permalink
[1996] Add an Expand All menu item contribution to the Explorer View
Browse files Browse the repository at this point in the history
Bug: #1996
Signed-off-by: Axel RICHARD <axel.richard@obeo.fr>
  • Loading branch information
AxelRICHARD authored and sbegaudeau committed May 31, 2023
1 parent c9d0a3b commit 390180f
Show file tree
Hide file tree
Showing 23 changed files with 721 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
- [x] Changelog updated

== Second task
- [ ] Implement expand all action in tree item context menu
- [x] Implement expand all action in tree item context menu
- [x] First version of the user interface done with Cypress tests
- [ ] User guide updated with screenshots

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,34 @@ describe('/projects/:projectId/edit - Document Context Menu', () => {

cy.getByTestId('explorerTree').contains('NewEntity');
});

it('expand all menu item', () => {
cy.getByTestId('robot').should('exist');
cy.getByTestId('Robot').should('not.exist');

cy.getByTestId('robot-more').click();
cy.getByTestId('treeitem-contextmenu').findByTestId('expand-all').click();

cy.getByTestId('robot').should('exist');
cy.getByTestId('Robot').should('exist');
cy.getByTestId('Central_Unit').should('exist');
cy.getByTestId('DSP').should('exist');
cy.getByTestId('standard').should('exist');
cy.getByTestId('Motion_Engine').should('exist');
cy.getByTestId('active').should('exist');
cy.getByTestId('CaptureSubSystem').should('exist');
cy.getByTestId('Radar_Capture').should('exist');
cy.getByTestId('high').should('exist');
cy.getByTestId('high').should('exist');
cy.getByTestId('Back_Camera').should('exist');
cy.getByTestId('standard').should('exist');
cy.getByTestId('Radar').should('exist');
cy.getByTestId('high').should('exist');
cy.getByTestId('Engine').should('exist');
cy.getByTestId('GPU').should('exist');
cy.getByTestId('standard').should('exist');
cy.getByTestId('active').should('exist');
cy.getByTestId('Wifi').should('exist');
cy.getByTestId('standard').should('exist');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,72 @@ describe('/projects/:projectId/edit - Object Context Menu', () => {
cy.getByTestId('Topography with auto layout').click();
cy.getByTestId('name').should('have.value', 'newName');
});

it('expand all menu item on root object', () => {
cy.getByTestId('robot').dblclick();
cy.getByTestId('Robot').should('exist');

cy.getByTestId('Robot-more').click();
cy.getByTestId('treeitem-contextmenu').findByTestId('expand-all').click();

cy.getByTestId('robot').should('exist');
cy.getByTestId('Robot').should('exist');
cy.getByTestId('Central_Unit').should('exist');
cy.getByTestId('DSP').should('exist');
cy.getByTestId('standard').should('exist');
cy.getByTestId('Motion_Engine').should('exist');
cy.getByTestId('active').should('exist');
cy.getByTestId('CaptureSubSystem').should('exist');
cy.getByTestId('Radar_Capture').should('exist');
cy.getByTestId('high').should('exist');
cy.getByTestId('high').should('exist');
cy.getByTestId('Back_Camera').should('exist');
cy.getByTestId('standard').should('exist');
cy.getByTestId('Radar').should('exist');
cy.getByTestId('high').should('exist');
cy.getByTestId('Engine').should('exist');
cy.getByTestId('GPU').should('exist');
cy.getByTestId('standard').should('exist');
cy.getByTestId('active').should('exist');
cy.getByTestId('Wifi').should('exist');
cy.getByTestId('standard').should('exist');
});

it('expand all menu item on intermediate object in tree hierarchy', () => {
cy.getByTestId('robot').dblclick();
cy.getByTestId('Robot').dblclick();

cy.getByTestId('Central_Unit').should('exist');
cy.getByTestId('Central_Unit-more').click();
cy.getByTestId('treeitem-contextmenu').findByTestId('expand-all').click();

cy.getByTestId('robot').should('exist');
cy.getByTestId('Robot').should('exist');
cy.getByTestId('Central_Unit').should('exist');
cy.getByTestId('DSP').should('exist');
cy.getByTestId('standard').should('exist');
cy.getByTestId('Motion_Engine').should('exist');
cy.getByTestId('active').should('exist');
cy.getByTestId('CaptureSubSystem').should('exist');
cy.getByTestId('Radar_Capture').should('not.exist');
cy.getByTestId('Back_Camera').should('not.exist');
cy.getByTestId('Radar').should('not.exist');
cy.getByTestId('Engine').should('not.exist');
cy.getByTestId('GPU').should('not.exist');
// Unable to test this tree item cause it has the same id than another existing one
// cy.getByTestId('active').should('not.exist');
cy.getByTestId('Wifi').should('exist');
// Unable to test this tree item cause it has the same id than another existing one
// cy.getByTestId('standard').should('not.exist');
});

it('expand all menu item on object with no children', () => {
cy.getByTestId('robot').dblclick();
cy.getByTestId('Robot').dblclick();
cy.getByTestId('Wifi').dblclick();
cy.getByTestId('standard').dblclick();

cy.getByTestId('standard-more').click();
cy.getByTestId('treeitem-contextmenu').findByTestId('expand-all').should('not.exist');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*******************************************************************************
* Copyright (c) 2023 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.sirius.web.services.explorer;

import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

import org.eclipse.sirius.components.collaborative.trees.api.IExpandAllTreePathProvider;
import org.eclipse.sirius.components.collaborative.trees.dto.ExpandAllTreePathInput;
import org.eclipse.sirius.components.collaborative.trees.dto.ExpandAllTreePathSuccessPayload;
import org.eclipse.sirius.components.collaborative.trees.dto.TreePath;
import org.eclipse.sirius.components.core.api.IEditingContext;
import org.eclipse.sirius.components.core.api.IObjectService;
import org.eclipse.sirius.components.core.api.IPayload;
import org.eclipse.sirius.components.emf.services.EditingContext;
import org.eclipse.sirius.components.trees.Tree;
import org.eclipse.sirius.web.services.explorer.api.IExplorerNavigationService;
import org.springframework.stereotype.Service;

/**
* Implementation of {@link IExpandAllTreePathProvider} for the Sirius Web Explorer.
*
* @author arichard
*/
@Service
public class ExplorerExpandAllTreePathProvider implements IExpandAllTreePathProvider {

private final IObjectService objectService;

private final IExplorerNavigationService explorerNavigationService;

public ExplorerExpandAllTreePathProvider(IObjectService objectService, IExplorerNavigationService explorerNavigationService) {
this.objectService = Objects.requireNonNull(objectService);
this.explorerNavigationService = Objects.requireNonNull(explorerNavigationService);
}

@Override
public boolean canHandle(Tree tree) {
return tree != null && Objects.equals(ExplorerDescriptionProvider.DESCRIPTION_ID, tree.getDescriptionId());
}

@Override
public IPayload handle(IEditingContext editingContext, Tree tree, ExpandAllTreePathInput input) {
int maxDepth = 0;
String treeItemId = input.treeItemId();
Set<String> treeItemIdsToExpand = new HashSet<>();
var object = this.objectService.getObject(editingContext, treeItemId);
if (object.isPresent()) {
// We need to get the current depth of the tree item
var itemAncestors = this.explorerNavigationService.getAncestors(editingContext, treeItemId);
maxDepth = itemAncestors.size();
maxDepth = this.addAllContents(editingContext, treeItemId, maxDepth, treeItemIdsToExpand);
} else {
// The object may be a document
var optionalEditingDomain = Optional.of(editingContext).filter(EditingContext.class::isInstance)
.map(EditingContext.class::cast)
.map(EditingContext::getDomain);

if (optionalEditingDomain.isPresent()) {
var optionalResource = optionalEditingDomain.get().getResourceSet().getResources().stream()
.filter(resource -> treeItemId.equals(resource.getURI().path().substring(1)))
.findFirst();
if (optionalResource.isPresent()) {
var contents = optionalResource.get().getContents();
if (!contents.isEmpty()) {
treeItemIdsToExpand.add(treeItemId);
for (var rootObject: contents) {
var rootObjectId = this.objectService.getId(rootObject);
var rootObjectTreePathMaxDepth = 1;
maxDepth = this.addAllContents(editingContext, rootObjectId, rootObjectTreePathMaxDepth, treeItemIdsToExpand);
maxDepth = Math.max(maxDepth, rootObjectTreePathMaxDepth);
}
}
}
}
}
return new ExpandAllTreePathSuccessPayload(input.id(), new TreePath(treeItemIdsToExpand.stream().toList(), maxDepth));
}

private int addAllContents(IEditingContext editingContext, String treeItemId, int depth, Set<String> treeItemIdsToExpand) {
var depthConsidered = depth;
var contents = this.objectService.getContents(editingContext, treeItemId);
if (!contents.isEmpty()) {
treeItemIdsToExpand.add(treeItemId);

for (var child: contents) {
String childId = this.objectService.getId(child);
treeItemIdsToExpand.add(childId);
var childTreePathMaxDepth = depth + 1;
childTreePathMaxDepth = this.addAllContents(editingContext, childId, childTreePathMaxDepth, treeItemIdsToExpand);
depthConsidered = Math.max(depthConsidered, childTreePathMaxDepth);
}
}

return depthConsidered;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*******************************************************************************
* Copyright (c) 2023 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.sirius.web.services.explorer;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;

import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.sirius.components.core.api.IEditingContext;
import org.eclipse.sirius.components.core.api.IObjectService;
import org.eclipse.sirius.web.services.api.representations.IRepresentationService;
import org.eclipse.sirius.web.services.api.representations.RepresentationDescriptor;
import org.eclipse.sirius.web.services.explorer.api.IExplorerNavigationService;
import org.springframework.stereotype.Service;

/**
* Services for the navigation through the Sirius Web Explorer.
*
* @author arichard
*/
@Service
public class ExplorerNavigationService implements IExplorerNavigationService {

private final IObjectService objectService;

private final IRepresentationService representationService;

public ExplorerNavigationService(IObjectService objectService, IRepresentationService representationService) {
this.objectService = Objects.requireNonNull(objectService);
this.representationService = Objects.requireNonNull(representationService);
}

@Override
public List<String> getAncestors(IEditingContext editingContext, String selectionEntryId) {
List<String> ancestorsIds = new ArrayList<>();

var optionalRepresentation = this.representationService.getRepresentation(UUID.fromString(selectionEntryId));
var optionalSemanticObject = this.objectService.getObject(editingContext, selectionEntryId);

Optional<Object> optionalObject = Optional.empty();
if (optionalRepresentation.isPresent()) {
// The first parent of a representation item is the item for its targetObject.
optionalObject = optionalRepresentation.map(RepresentationDescriptor::getTargetObjectId)
.flatMap(objectId -> this.objectService.getObject(editingContext, objectId));
} else if (optionalSemanticObject.isPresent()) {
// The first parent of a semantic object item is the item for its actual container
optionalObject = optionalSemanticObject.filter(EObject.class::isInstance)
.map(EObject.class::cast)
.map(eObject -> Optional.<Object> ofNullable(eObject.eContainer()).orElse(eObject.eResource()));
}

while (optionalObject.isPresent()) {
ancestorsIds.add(this.getItemId(optionalObject.get()));
optionalObject = optionalObject
.filter(EObject.class::isInstance)
.map(EObject.class::cast)
.map(eObject -> Optional.<Object>ofNullable(eObject.eContainer()).orElse(eObject.eResource()));
}
return ancestorsIds;
}

private String getItemId(Object object) {
String result = null;
if (object instanceof Resource) {
Resource resource = (Resource) object;
result = resource.getURI().path().substring(1);
} else if (object instanceof EObject) {
result = this.objectService.getId(object);
}
return result;
}
}

0 comments on commit 390180f

Please sign in to comment.