Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ public enum Permission {
CAN_DELETE_CHECKIN_DOCUMENT("Delete check-ins document", "Check-ins"),
CAN_VIEW_ALL_CHECKINS("View all check-ins", "Check-ins"),
CAN_UPDATE_ALL_CHECKINS("Update all check-ins, including completed check-ins", "Check-ins"),
CAN_EDIT_SKILL_CATEGORIES("Edit skill categories", "Skill Categories");
CAN_EDIT_SKILL_CATEGORIES("Edit skill categories", "Skill Categories"),
CAN_CREATE_REVIEW_ASSIGNMENTS("Create review assignments", "Reviews");

private final String description;
private final String category;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.objectcomputing.checkins.services.reviews;

import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.data.annotation.AutoPopulated;
import io.micronaut.data.annotation.TypeDef;
import io.micronaut.data.model.DataType;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.NotBlank;
import java.util.Objects;
import java.util.UUID;

@Entity
@Setter
@Getter
@Introspected
@NoArgsConstructor
@Table(name = "review_assignments")
public class ReviewAssignment {

@Id
@Column(name = "id")
@AutoPopulated
@TypeDef(type = DataType.STRING)
@Schema(description = "The id of the review assignment", required = true)
private UUID id;

public ReviewAssignment(UUID revieweeId, UUID reviewerId, UUID reviewPeriodId, Boolean approved) {
this.revieweeId = revieweeId;
this.reviewerId = reviewerId;
this.reviewPeriodId = reviewPeriodId;
this.approved = approved;
}

@NotBlank
@Column(name = "reviewee_id")
@Schema(required = true, description = "The ID of the employee being reviewed")
private UUID revieweeId;

@NotBlank
@Column(name = "reviewer_id")
@Schema(required = true, description = "The ID of the employee conducting the review")
private UUID reviewerId;

@NotBlank
@Column(name = "review_period_id")
@Schema(required = true, description = "The ID of the review period that the assignment is related to")
private UUID reviewPeriodId;

@Nullable
@Column(name = "approved")
@Schema(description = "The status of the review assignment")
private Boolean approved;

@Override
public int hashCode() {
return Objects.hash(id, revieweeId, reviewerId, reviewPeriodId, approved);
}

@Override
public String toString() {
return "ReviewAssignment{" +
"id=" + id +
", revieweeId=" + revieweeId +
", reviewerId=" + reviewerId +
", reviewPeriodId=" + reviewPeriodId +
", approved=" + approved +
'}';
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ReviewAssignment that = (ReviewAssignment) o;
return Objects.equals(id, that.id) &&
Objects.equals(revieweeId, that.revieweeId) &&
Objects.equals(reviewerId, that.reviewerId) &&
Objects.equals(reviewPeriodId, that.reviewPeriodId) &&
Objects.equals(approved, that.approved);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.objectcomputing.checkins.services.reviews;

import com.objectcomputing.checkins.exceptions.NotFoundException;
import com.objectcomputing.checkins.services.permissions.Permission;
import com.objectcomputing.checkins.services.permissions.RequiredPermission;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.*;
import io.micronaut.scheduling.TaskExecutors;
import io.micronaut.security.annotation.Secured;
import io.micronaut.security.rules.SecurityRule;
import io.netty.channel.EventLoopGroup;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.inject.Named;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.net.URI;
import java.util.UUID;
import java.util.concurrent.ExecutorService;

@Controller("/services/review-assignments")
@Secured(SecurityRule.IS_AUTHENTICATED)
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Tag(name = "reviews")
public class ReviewAssignmentController {

private ReviewAssignmentServices reviewAssignmentServices;
private final EventLoopGroup eventLoopGroup;
private final ExecutorService ioExecutorService;

public ReviewAssignmentController(ReviewAssignmentServices reviewAssignmentServices, EventLoopGroup eventLoopGroup, @Named(TaskExecutors.IO) ExecutorService ioExecutorService) {
this.reviewAssignmentServices = reviewAssignmentServices;
this.eventLoopGroup = eventLoopGroup;
this.ioExecutorService = ioExecutorService;
}

/**
* Create and save a new {@link ReviewAssignment}.
*
* @param assignment a {@link ReviewAssignmentDTO} representing the desired review assignment
* @return a streamable response containing the stored {@link ReviewAssignment}
*/
@Post
@RequiredPermission(Permission.CAN_CREATE_REVIEW_ASSIGNMENTS)
public Mono<HttpResponse<ReviewAssignment>> createReviewAssignment(@Body @Valid ReviewAssignmentDTO assignment, HttpRequest<ReviewAssignmentDTO> request) {

return Mono.fromCallable(() -> reviewAssignmentServices.save(assignment.convertToEntity()))
.publishOn(Schedulers.fromExecutor(eventLoopGroup))
.map(reviewAssignment -> (HttpResponse<ReviewAssignment>) HttpResponse.created(reviewAssignment)
.headers(headers -> headers.location(
URI.create(String.format("%s/%s", request.getPath(), reviewAssignment.getId())))))
.subscribeOn(Schedulers.fromExecutor(ioExecutorService));

}

/**
* Retrieve a {@link ReviewAssignment} given its id.
*
* @param id {@link UUID} of the review assignment
* @return a streamable response containing the found {@link ReviewAssignment} with the given ID
*/

@Get("/{id}")
public Mono<HttpResponse<ReviewAssignment>> getById(@NotNull UUID id) {

return Mono.fromCallable(() -> {
ReviewAssignment result = reviewAssignmentServices.findById(id);
if (result == null) {
throw new NotFoundException("No review assignment for UUID");
}
return result;
}).publishOn(Schedulers.fromExecutor(eventLoopGroup)).map(reviewAssignment -> {
return (HttpResponse<ReviewAssignment>) HttpResponse.ok(reviewAssignment);
}).subscribeOn(Schedulers.fromExecutor(ioExecutorService));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.objectcomputing.checkins.services.reviews;

import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.annotation.Nullable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.validation.constraints.NotBlank;
import java.util.UUID;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Introspected
public class ReviewAssignmentDTO {

@NotBlank
@Schema(required = true, description = "The ID of the employee being reviewed")
private UUID revieweeId;

@NotBlank
@Schema(required = true, description = "The ID of the employee conducting the review")
private UUID reviewerId;

@NotBlank
@Schema(required = true, description = "The ID of the review period that the assignment is related to")
private UUID reviewPeriodId;

@Nullable
@Schema(description = "The status of the review assignment")
private Boolean approved = false;

public ReviewAssignment convertToEntity(){
return new ReviewAssignment(this.revieweeId, this.reviewerId, this.reviewPeriodId, this.approved);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.objectcomputing.checkins.services.reviews;

import io.micronaut.data.jdbc.annotation.JdbcRepository;
import io.micronaut.data.model.query.builder.sql.Dialect;
import io.micronaut.data.repository.CrudRepository;

import java.util.UUID;

@JdbcRepository(dialect = Dialect.POSTGRES)
public interface ReviewAssignmentRepository extends CrudRepository<ReviewAssignment, UUID> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.objectcomputing.checkins.services.reviews;

import java.util.UUID;

public interface ReviewAssignmentServices {
ReviewAssignment save(ReviewAssignment reviewAssignment);
ReviewAssignment findById(UUID id);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.objectcomputing.checkins.services.reviews;

import com.objectcomputing.checkins.exceptions.AlreadyExistsException;
import com.objectcomputing.checkins.exceptions.BadArgException;
import jakarta.inject.Singleton;

import javax.validation.constraints.NotNull;
import java.util.UUID;

@Singleton
public class ReviewAssignmentServicesImpl implements ReviewAssignmentServices {

ReviewAssignmentRepository reviewAssignmentRepository;

public ReviewAssignmentServicesImpl(ReviewAssignmentRepository reviewAssignmentRepository) {
this.reviewAssignmentRepository = reviewAssignmentRepository;
}

@Override
public ReviewAssignment save(ReviewAssignment reviewAssignment) {
ReviewAssignment newAssignment = null;
if (reviewAssignment != null) {

if (reviewAssignment.getId() != null) {
throw new BadArgException(String.format("Found unexpected id %s for review assignment. New entities must not contain an id.",
reviewAssignment.getId()));
}

//The service creates a new review assignment with an initial approved status of false.
reviewAssignment.setApproved(false);

newAssignment = reviewAssignmentRepository.save(reviewAssignment);
}

return newAssignment;
}

@Override
public ReviewAssignment findById(@NotNull UUID id) {
return reviewAssignmentRepository.findById(id).orElse(null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
DROP TABLE IF EXISTS review_assignments;

CREATE TABLE review_assignments (
id varchar PRIMARY KEY,
reviewee_id varchar,
reviewer_id varchar,
review_period_id varchar,
approved boolean
);
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ public interface PermissionFixture extends RolePermissionFixture {
Permission.CAN_UPDATE_CHECKIN_DOCUMENT,
Permission.CAN_DELETE_CHECKIN_DOCUMENT,
Permission.CAN_VIEW_ALL_CHECKINS,
Permission.CAN_UPDATE_ALL_CHECKINS
Permission.CAN_UPDATE_ALL_CHECKINS,
Permission.CAN_CREATE_REVIEW_ASSIGNMENTS
);


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.objectcomputing.checkins.services.pulseresponse.PulseResponseRepository;
import com.objectcomputing.checkins.services.question_category.QuestionCategoryRepository;
import com.objectcomputing.checkins.services.questions.QuestionRepository;
import com.objectcomputing.checkins.services.reviews.ReviewAssignmentRepository;
import com.objectcomputing.checkins.services.reviews.ReviewPeriodRepository;
import com.objectcomputing.checkins.services.role.RoleRepository;
import com.objectcomputing.checkins.services.role.member_roles.MemberRoleRepository;
Expand Down Expand Up @@ -177,6 +178,10 @@ default ReviewPeriodRepository getReviewPeriodRepository() {
return getEmbeddedServer().getApplicationContext().getBean(ReviewPeriodRepository.class);
}

default ReviewAssignmentRepository getReviewAssignmentRepository() {
return getEmbeddedServer().getApplicationContext().getBean(ReviewAssignmentRepository.class);
}

default SkillCategoryRepository getSkillCategoryRepository() {
return getEmbeddedServer().getApplicationContext().getBean(SkillCategoryRepository.class);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.objectcomputing.checkins.services.fixture;

import com.objectcomputing.checkins.services.reviews.ReviewAssignment;
import com.objectcomputing.checkins.services.reviews.ReviewPeriod;
import com.objectcomputing.checkins.services.reviews.ReviewStatus;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.UUID;

public interface ReviewAssignmentFixture extends RepositoryFixture {


default ReviewAssignment createADefaultReviewAssignment() {

return getReviewAssignmentRepository().save(new ReviewAssignment(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), false));

}

}
Loading