Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TACKLE-192] - Bulk copy Assessment/Review #142

Merged
merged 5 commits into from
Oct 29, 2021
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
10 changes: 10 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-flyway</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx</artifactId>
</dependency>
<!-- todo remove Hibernate Validator? -->
<dependency>
<groupId>io.quarkus</groupId>
Expand Down Expand Up @@ -203,6 +207,12 @@
<version>2.30.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>4.1.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.tackle.applicationinventory.dto;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;

public class BulkReviewDto {

private Long id;

@NotNull
private Long sourceReview;

@NotEmpty
private List<Long> targetApplications;

private boolean completed;

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public Long getSourceReview() {
return sourceReview;
}

public void setSourceReview(Long sourceReview) {
this.sourceReview = sourceReview;
}

public List<Long> getTargetApplications() {
return targetApplications;
}

public void setTargetApplications(List<Long> targetApplications) {
this.targetApplications = targetApplications;
}

public boolean isCompleted() {
return completed;
}

public void setCompleted(boolean completed) {
this.completed = completed;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.tackle.applicationinventory.dto.utils;

import io.tackle.applicationinventory.dto.BulkReviewDto;
import io.tackle.applicationinventory.entities.BulkCopyReview;

import java.util.stream.Collectors;

public class EntityToDTO {

public static BulkReviewDto toDTO(BulkCopyReview entity) {
BulkReviewDto dto = new BulkReviewDto();

dto.setId(entity.id);
dto.setCompleted(entity.completed);
dto.setSourceReview(entity.sourceReview.id);
dto.setTargetApplications(entity.targetApplications.stream()
.map(f -> f.application.id)
.collect(Collectors.toList())
);

return dto;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.tackle.applicationinventory.entities;

import io.tackle.commons.entities.AbstractEntity;
import org.hibernate.annotations.ResultCheckStyle;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

@Entity
@Table(name = "bulk_copy_review")
@SQLDelete(sql = "UPDATE bulk_copy_review SET deleted = true WHERE id = ?", check = ResultCheckStyle.COUNT)
@Where(clause = "deleted = false")
public class BulkCopyReview extends AbstractEntity {

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(foreignKey = @ForeignKey, name = "review_id")
public Review sourceReview;

@OneToMany(fetch = FetchType.LAZY, cascade = {CascadeType.ALL}, orphanRemoval = true, mappedBy = "bulkCopyReview")
public Set<BulkCopyReviewDetails> targetApplications = new HashSet<>();

public boolean completed = false;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.tackle.applicationinventory.entities;

import io.tackle.commons.entities.AbstractEntity;
import org.hibernate.annotations.ResultCheckStyle;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;

import javax.persistence.*;

@Entity
@Table(name = "bulk_copy_review_details")
@SQLDelete(sql = "UPDATE bulk_copy_review_details SET deleted = true WHERE id = ?", check = ResultCheckStyle.COUNT)
@Where(clause = "deleted = false")
public class BulkCopyReviewDetails extends AbstractEntity {

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(foreignKey = @ForeignKey, name = "bulk_copy_review_id")
public BulkCopyReview bulkCopyReview;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(foreignKey = @ForeignKey, name = "application_id")
public Application application;

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,8 @@ public class Review extends AbstractEntity {
@OneToOne(fetch = FetchType.LAZY, optional = false)
@JsonIgnoreProperties(value = {"review"}, allowSetters = true)
public Application application;

// No foreign keys associated to this field since foreign keys might
// complicate DELETE operations.
public Long copiedFromReviewId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package io.tackle.applicationinventory.resources;

import io.tackle.applicationinventory.dto.BulkReviewDto;
import io.tackle.applicationinventory.dto.utils.EntityToDTO;
import io.tackle.applicationinventory.entities.Application;
import io.tackle.applicationinventory.entities.BulkCopyReview;
import io.tackle.applicationinventory.entities.BulkCopyReviewDetails;
import io.tackle.applicationinventory.entities.Review;
import io.tackle.applicationinventory.services.BulkCopyReviewService;
import io.vertx.core.eventbus.EventBus;

import javax.inject.Inject;
import javax.transaction.NotSupportedException;
import javax.transaction.*;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.ws.rs.*;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Path("/review/bulk")
@Produces("application/json")
@Consumes("application/json")
public class ReviewBulkCopyResource {

@Inject
UserTransaction transaction;

@Inject
EventBus eventBus;

private void handleSimpleRollback(UserTransaction transaction) {
try {
transaction.rollback();
} catch (SystemException e) {
throw new IllegalStateException(e);
}
}

@POST
public BulkReviewDto createBulkCopyReview(
@NotNull @Valid BulkReviewDto bulkReviewInput
) throws SystemException, NotSupportedException, HeuristicRollbackException, HeuristicMixedException, RollbackException {
transaction.begin();

// Find source review
Long sourceReviewId = bulkReviewInput.getSourceReview();
Review sourceReview = Review.<Review>findByIdOptional(sourceReviewId)
.orElseThrow(() -> {
handleSimpleRollback(transaction);
return new BadRequestException("Source review not valid");
});

// Find target applications
List<Application> applicationsTarget = bulkReviewInput.getTargetApplications().stream()
.map(appId -> Application.<Application>findById(appId))
.collect(Collectors.toList());
if (applicationsTarget.stream().anyMatch(Objects::isNull)) {
handleSimpleRollback(transaction);
throw new BadRequestException("One or more target applications is not valid");
}

// Prepare JPA object to be persisted
BulkCopyReview bulkCopyReviewOutput = new BulkCopyReview();

bulkCopyReviewOutput.sourceReview = sourceReview;
bulkCopyReviewOutput.targetApplications = applicationsTarget.stream()
.map(application -> {
BulkCopyReviewDetails detail = new BulkCopyReviewDetails();
detail.bulkCopyReview = bulkCopyReviewOutput;
detail.application = application;

return detail;
})
.collect(Collectors.toSet());
bulkCopyReviewOutput.completed = false;

bulkCopyReviewOutput.persist();
transaction.commit();

// Fire bus event
eventBus.send(BulkCopyReviewService.BUS_EVENT, bulkCopyReviewOutput.id);

// Generate response
return EntityToDTO.toDTO(bulkCopyReviewOutput);
}

@GET
@Path("/{id}")
public BulkReviewDto getBulkCopyReview(@PathParam("id") Long id) {
BulkCopyReview entity = BulkCopyReview
.<BulkCopyReview>findByIdOptional(id)
.orElseThrow(NotFoundException::new);

return EntityToDTO.toDTO(entity);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package io.tackle.applicationinventory.services;

import io.quarkus.vertx.ConsumeEvent;
import io.smallrye.common.annotation.Blocking;
import io.tackle.applicationinventory.entities.Application;
import io.tackle.applicationinventory.entities.BulkCopyReview;
import io.tackle.applicationinventory.entities.Review;

import javax.enterprise.context.ApplicationScoped;
import javax.transaction.Transactional;

@ApplicationScoped
public class BulkCopyReviewService {

public static final String BUS_EVENT = "process-bulk-review-copy";

@Transactional
@ConsumeEvent(BUS_EVENT)
@Blocking
public void processBulkCopyReview(Long bulkId) {
BulkCopyReview bulk = BulkCopyReview.findById(bulkId);

// Copy review to all apps
bulk.targetApplications.forEach(detail -> {
Application application = Application.findById(detail.application.id);

Review oldReview = Review.find("application.id", detail.application.id).firstResult();
if (oldReview != null) {
oldReview = copyReviewFromSourceToTarget(bulk.sourceReview, oldReview);
oldReview.persist();
} else {
Review newReview = copyReviewFromSourceToTarget(bulk.sourceReview, new Review());
newReview.application = application;
newReview.persist();
}
});

bulk.completed = true;
bulk.persist();
}

private Review copyReviewFromSourceToTarget(Review source, Review target) {
target.proposedAction = source.proposedAction;
target.effortEstimate = source.effortEstimate;
target.businessCriticality = source.businessCriticality;
target.workPriority = source.workPriority;
target.comments = source.comments;

target.copiedFromReviewId = source.id;
return target;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
create table bulk_copy_review
(
id int8 not null,

createTime timestamp,
createUser varchar(255),
deleted boolean,
updateTime timestamp,
updateUser varchar(255),

completed boolean,
review_id int8 not null,
primary key (id)
);

create table bulk_copy_review_details
(
id int8 not null,

createTime timestamp,
createUser varchar(255),
deleted boolean,
updateTime timestamp,
updateUser varchar(255),

bulk_copy_review_id int8 not null,
application_id int8 not null,
primary key (id)
);

alter table if exists bulk_copy_review_details
add constraint fk_bulk_copy_review_details_application
foreign key (application_id)
references application;

-- Add audit data

alter table if exists review
add column copiedFromReviewId int8;
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@
@QuarkusTest
@TestProfile(FlywayMigrationProfile.class)
public class FlywayMigrationTest {

@Inject
Flyway flyway;

@Test
public void testMigration() {
// check the number of migrations applied equals the number of files in resources/db/migration folder
assertEquals(18, flyway.info().applied().length);
assertEquals(19, flyway.info().applied().length);
// check the current migration version is the one from the last file in resources/db/migration folder
assertEquals("20210914.1", flyway.info().current().getVersion().toString());
assertEquals("20211008.1", flyway.info().current().getVersion().toString());
// just a basic test to double check the application started
// to prove the flyway scripts ran successfully during startup
given()
Expand Down
Loading