Skip to content

Commit

Permalink
[3630] Restore support for the deletion of dangling representations
Browse files Browse the repository at this point in the history
Bug: #3630
Signed-off-by: Stéphane Bégaudeau <[email protected]>
  • Loading branch information
sbegaudeau committed Jun 25, 2024
1 parent b047d9e commit 8cb6686
Show file tree
Hide file tree
Showing 11 changed files with 189 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ More existing APIs will be migrated to this new common pattern.
- https://github.com/eclipse-sirius/sirius-web/issues/3531[#3531] [diagram] Fix unnecessary edges label re render
- https://github.com/eclipse-sirius/sirius-web/issues/3650[#3650] [diagram] Fix potential NPE in DiagramNavigator and Node toString method
- https://github.com/eclipse-sirius/sirius-web/issues/3649[#3649] [sirius-web] Restore support for Related elements view icons
- https://github.com/eclipse-sirius/sirius-web/issues/3630[#3630] [sirius-web] Restore support for the deletion of dangling representations

=== New Features

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2021 Obeo.
* Copyright (c) 2021, 2024 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
Expand Down Expand Up @@ -35,7 +35,7 @@ public interface IDanglingRepresentationDeletionService {
*/
boolean isDangling(IEditingContext editingContext, IRepresentation representation);

void deleteDanglingRepresentations(String editingContextId);
void deleteDanglingRepresentations(IEditingContext editingContext);

/**
* Implementation which does nothing, used for mocks in unit tests.
Expand All @@ -50,7 +50,7 @@ public boolean isDangling(IEditingContext editingContext, IRepresentation repres
}

@Override
public void deleteDanglingRepresentations(String editingContextId) {
public void deleteDanglingRepresentations(IEditingContext editingContext) {
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ private Disposable setupChangeDescriptionSinkConsumer() {
if (this.shouldPersistTheEditingContext(changeDescription)) {
this.editingContextPersistenceService.persist(this.editingContext);
}
this.danglingRepresentationDeletionService.deleteDanglingRepresentations(this.editingContext.getId());
this.danglingRepresentationDeletionService.deleteDanglingRepresentations(this.editingContext);

var timer = this.meterRegistry.timer(Monitoring.TIMER_REFRESH_REPRESENTATION, "changeDescription", changeDescription.getSourceId());
refreshRepresentationSample.stop(timer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@
import org.eclipse.sirius.components.core.api.IEditingContext;
import org.eclipse.sirius.components.core.api.IObjectSearchService;
import org.eclipse.sirius.components.representations.IRepresentation;
import org.eclipse.sirius.web.application.UUIDParser;
import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.services.api.IRepresentationDataDeletionService;
import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.services.api.IRepresentationDataSearchService;
import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.projections.RepresentationDataMetadataOnly;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
* Used to delete dangling representations.
Expand All @@ -31,8 +37,14 @@ public class DanglingRepresentationDeletionService implements IDanglingRepresent

private final IObjectSearchService objectSearchService;

public DanglingRepresentationDeletionService(IObjectSearchService objectSearchService) {
private final IRepresentationDataSearchService representationDataSearchService;

private final IRepresentationDataDeletionService representationDataDeletionService;

public DanglingRepresentationDeletionService(IObjectSearchService objectSearchService, IRepresentationDataSearchService representationDataSearchService, IRepresentationDataDeletionService representationDataDeletionService) {
this.objectSearchService = Objects.requireNonNull(objectSearchService);
this.representationDataSearchService = Objects.requireNonNull(representationDataSearchService);
this.representationDataDeletionService = Objects.requireNonNull(representationDataDeletionService);
}

@Override
Expand All @@ -43,7 +55,13 @@ public boolean isDangling(IEditingContext editingContext, IRepresentation repres
}

@Override
public void deleteDanglingRepresentations(String editingContextId) {
// Do nothing for now
@Transactional
public void deleteDanglingRepresentations(IEditingContext editingContext) {
new UUIDParser().parse(editingContext.getId()).ifPresent(projectId -> {
this.representationDataSearchService.findAllMetadataByProject(AggregateReference.to(projectId)).stream()
.filter(representationMetadata -> this.objectSearchService.getObject(editingContext, representationMetadata.targetObjectId()).isEmpty())
.map(RepresentationDataMetadataOnly::id)
.forEach(this.representationDataDeletionService::delete);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*******************************************************************************
* Copyright (c) 2024, 2024 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.domain.boundedcontexts.representationdata.projections;

import java.util.UUID;

/**
* Projection used to retrieve only the representation metadata of the representation data.
*
* @author sbegaudeau
*/
public record RepresentationDataMetadataOnly(
UUID id,
String label,
String kind,
String targetObjectId,
String descriptionId) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.util.UUID;

import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.RepresentationData;
import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.projections.RepresentationDataMetadataOnly;
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.ListCrudRepository;
import org.springframework.data.repository.ListPagingAndSortingRepository;
Expand All @@ -36,6 +37,13 @@ public interface IRepresentationDataRepository extends ListPagingAndSortingRepos
""")
List<RepresentationData> findAllByProjectId(UUID projectId);

@Query("""
SELECT id, label, kind, target_object_id, description_id
FROM representation_data representationData
WHERE representationData.project_id = :projectId
""")
List<RepresentationDataMetadataOnly> findAllMetadataByProjectId(UUID projectId);

@Query("""
SELECT representationData.project_id
FROM representation_data representationData
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import org.eclipse.sirius.web.domain.boundedcontexts.project.Project;
import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.RepresentationData;
import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.projections.RepresentationDataMetadataOnly;
import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.repositories.IRepresentationDataRepository;
import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.services.api.IRepresentationDataSearchService;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
Expand Down Expand Up @@ -53,6 +54,11 @@ public List<RepresentationData> findAllByProject(AggregateReference<Project, UUI
return this.representationDataRepository.findAllByProjectId(project.getId());
}

@Override
public List<RepresentationDataMetadataOnly> findAllMetadataByProject(AggregateReference<Project, UUID> project) {
return this.representationDataRepository.findAllMetadataByProjectId(project.getId());
}

@Override
public boolean existAnyRepresentationForTargetObjectId(String targetObjectId) {
return this.representationDataRepository.existAnyRepresentationForTargetObjectId(targetObjectId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import org.eclipse.sirius.web.domain.boundedcontexts.project.Project;
import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.RepresentationData;
import org.eclipse.sirius.web.domain.boundedcontexts.representationdata.projections.RepresentationDataMetadataOnly;
import org.springframework.data.jdbc.core.mapping.AggregateReference;

/**
Expand All @@ -33,6 +34,8 @@ public interface IRepresentationDataSearchService {

List<RepresentationData> findAllByProject(AggregateReference<Project, UUID> project);

List<RepresentationDataMetadataOnly> findAllMetadataByProject(AggregateReference<Project, UUID> project);

boolean existAnyRepresentationForTargetObjectId(String targetObjectId);

List<RepresentationData> findAllByTargetObjectId(String targetObjectId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,8 @@ public boolean isDangling(IEditingContext editingContext, IRepresentation repres
}

@Override
public void deleteDanglingRepresentations(String editingContextId) {
new IDParser().parse(editingContextId).ifPresent(this.representationRepository::deleteDanglingRepresentations);
public void deleteDanglingRepresentations(IEditingContext editingContext) {
new IDParser().parse(editingContext.getId()).ifPresent(this.representationRepository::deleteDanglingRepresentations);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*******************************************************************************
* Copyright (c) 2024 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.application.controllers.representations;

import static org.assertj.core.api.Assertions.assertThat;

import java.time.Duration;
import java.util.Optional;
import java.util.UUID;
import java.util.function.BiFunction;
import java.util.function.Predicate;

import org.eclipse.sirius.components.collaborative.api.ChangeDescription;
import org.eclipse.sirius.components.collaborative.api.ChangeKind;
import org.eclipse.sirius.components.collaborative.portals.dto.PortalEventInput;
import org.eclipse.sirius.components.collaborative.portals.dto.PortalRefreshedEventPayload;
import org.eclipse.sirius.components.core.api.IEditingContext;
import org.eclipse.sirius.components.core.api.IInput;
import org.eclipse.sirius.components.core.api.IPayload;
import org.eclipse.sirius.components.core.api.SuccessPayload;
import org.eclipse.sirius.components.emf.services.api.IEMFEditingContext;
import org.eclipse.sirius.components.graphql.tests.ExecuteEditingContextFunctionInput;
import org.eclipse.sirius.components.graphql.tests.ExecuteEditingContextFunctionRunner;
import org.eclipse.sirius.components.portals.tests.graphql.PortalEventSubscriptionRunner;
import org.eclipse.sirius.web.AbstractIntegrationTests;
import org.eclipse.sirius.web.data.TestIdentifiers;
import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState;
import org.junit.jupiter.api.BeforeEach;
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.test.context.transaction.TestTransaction;
import org.springframework.transaction.annotation.Transactional;

import graphql.execution.DataFetcherResult;
import reactor.test.StepVerifier;

/**
* Used to test the proper deletion of dangling representations.
*
* @author sbegaudeau
*/
@Transactional
@SuppressWarnings("checkstyle:MultipleStringLiterals")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DanglingRepresentationControllerIntegrationTests extends AbstractIntegrationTests {

@Autowired
private IGivenInitialServerState givenInitialServerState;

@Autowired
private PortalEventSubscriptionRunner portalEventSubscriptionRunner;

@Autowired
private ExecuteEditingContextFunctionRunner executeEditingContextFunctionRunner;

@BeforeEach
public void beforeEach() {
this.givenInitialServerState.initialize();
}

@Test
@DisplayName("Given a project, when we delete an object with a child representation, then the representation is deleted")
@Sql(scripts = { "/scripts/initialize.sql" }, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = { "/scripts/cleanup.sql" }, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))
public void givenProjectWhenWeDeleteAnObjectWithChildRepresentationThenTheRepresentationIsDeleted() {
var portalEventInput = new PortalEventInput(UUID.randomUUID(), TestIdentifiers.ECORE_SAMPLE_PROJECT.toString(), TestIdentifiers.EPACKAGE_PORTAL_REPRESENTATION.toString());
var portalFlux = this.portalEventSubscriptionRunner.run(portalEventInput);

Predicate<Object> portalContentMatcher = object -> Optional.of(object)
.filter(DataFetcherResult.class::isInstance)
.map(DataFetcherResult.class::cast)
.map(DataFetcherResult::getData)
.filter(PortalRefreshedEventPayload.class::isInstance)
.isPresent();

Runnable deleteProjectContent = () -> {
BiFunction<IEditingContext, IInput, IPayload> function = (editingContext, input) -> {
if (editingContext instanceof IEMFEditingContext emfEditingContext) {
emfEditingContext.getDomain().getResourceSet().getResources().forEach(resource -> resource.getContents().clear());
}
return new SuccessPayload(input.id());
};

var inputId = UUID.randomUUID();
var changeDescription = new ChangeDescription(ChangeKind.SEMANTIC_CHANGE, TestIdentifiers.ECORE_SAMPLE_PROJECT.toString(), () -> inputId);
var input = new ExecuteEditingContextFunctionInput(inputId, TestIdentifiers.ECORE_SAMPLE_PROJECT.toString(), function, changeDescription);
var payload = this.executeEditingContextFunctionRunner.execute(input).block();

assertThat(payload).isInstanceOf(SuccessPayload.class);

TestTransaction.flagForCommit();
TestTransaction.end();
TestTransaction.start();
};

StepVerifier.create(portalFlux)
.expectNextMatches(portalContentMatcher)
.then(deleteProjectContent)
.expectComplete()
.verify(Duration.ofSeconds(10));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql;
Expand Down Expand Up @@ -146,8 +144,6 @@ private record TreeItemMatcher(Function<Tree, TreeItem> treeItemFinder, Predicat
@Autowired
private IIdentityService identityService;

private final Logger logger = LoggerFactory.getLogger(TreeControllerIntegrationTests.class);

@BeforeEach
public void beforeEach() {
this.givenInitialServerState.initialize();
Expand Down Expand Up @@ -240,8 +236,6 @@ public void givenExplorerOfProjectWhenWeRenameTreeItemThenTheTreeIsRefreshed() {
var portalFlux = this.portalEventSubscriptionRunner.run(portalEventInput);

Predicate<Object> portalRefreshedEventPayloadMatcher = object -> {
this.logger.info("portal refreshed event payload matcher");
this.logger.info(object.toString());
return Optional.of(object)
.filter(DataFetcherResult.class::isInstance)
.map(DataFetcherResult.class::cast)
Expand Down Expand Up @@ -298,8 +292,6 @@ private Predicate<Object> getTreeRefreshedEventPayloadMatcher(List<TreeItemMatch
});

return object -> {
this.logger.info("tree refreshed event payload matcher");
this.logger.info(object.toString());
return Optional.of(object)
.filter(DataFetcherResult.class::isInstance)
.map(DataFetcherResult.class::cast)
Expand Down

0 comments on commit 8cb6686

Please sign in to comment.