From 09f8ccd658cd933069dfe9c344b274a3b9c18a31 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Wed, 14 Aug 2024 09:10:35 -0500 Subject: [PATCH 01/55] Initial attempt at writing an upload controller. --- .../reports/ReportDataUploadController.java | 55 ++++++++++ .../reports/ReportDataUploadServices.java | 10 ++ .../reports/ReportDataUploadServicesImpl.java | 101 ++++++++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadController.java create mode 100644 server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServices.java create mode 100644 server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServicesImpl.java diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadController.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadController.java new file mode 100644 index 000000000..e9ee0d07f --- /dev/null +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadController.java @@ -0,0 +1,55 @@ +package com.objectcomputing.checkins.services.reports; + +import com.objectcomputing.checkins.exceptions.NotFoundException; +import io.micronaut.http.MediaType; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Post; +import io.micronaut.http.multipart.CompletedFileUpload; +import io.micronaut.scheduling.TaskExecutors; +import io.micronaut.scheduling.annotation.ExecuteOn; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; +import io.micronaut.http.annotation.Part; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; +import reactor.core.publisher.Flux; +import reactor.core.scheduler.Schedulers; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.io.IOException; + +@Controller("/services/report/data") +@ExecuteOn(TaskExecutors.BLOCKING) +@Secured(SecurityRule.IS_AUTHENTICATED) +public class ReportDataUploadController { + private static final Logger LOG = LoggerFactory.getLogger(ReportDataUploadController.class); + private final ReportDataUploadServices reportDataUploadServices; + + public ReportDataUploadController(ReportDataUploadServices reportDataUploadServices) { + this.reportDataUploadServices = reportDataUploadServices; + } + + /** + * Parse the CSV file and store it to employee hours table + * @param file + * @{@link HttpResponse} + */ + @Post(uri="/upload" , consumes = MediaType.MULTIPART_FORM_DATA) + public Mono> upload(@Part("file") Publisher file){ + return Flux.from(file) + .subscribeOn(Schedulers.boundedElastic()) + .map(part -> { + try { + reportDataUploadServices.store(part); + return part.getFilename(); + } catch(IOException ex) { + LOG.error(ex.toString()); + return ""; + } + }) + .collectList(); + } +} diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServices.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServices.java new file mode 100644 index 000000000..4bf382408 --- /dev/null +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServices.java @@ -0,0 +1,10 @@ +package com.objectcomputing.checkins.services.reports; + +import io.micronaut.http.multipart.CompletedFileUpload; +import java.io.IOException; + +public interface ReportDataUploadServices { + + void store(CompletedFileUpload file) throws IOException; + +} diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServicesImpl.java new file mode 100644 index 000000000..aa8cd089a --- /dev/null +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServicesImpl.java @@ -0,0 +1,101 @@ +package com.objectcomputing.checkins.services.reports; + +import com.objectcomputing.checkins.exceptions.BadArgException; +import com.objectcomputing.checkins.services.memberprofile.MemberProfile; +import com.objectcomputing.checkins.services.memberprofile.MemberProfileRepository; +import com.objectcomputing.checkins.services.memberprofile.currentuser.CurrentUserServices; +import com.objectcomputing.checkins.services.memberprofile.memberphoto.MemberPhotoServiceImpl; +import io.micronaut.http.multipart.CompletedFileUpload; +import jakarta.inject.Singleton; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.UUID; +import java.util.Date; +import java.util.HashMap; +import java.util.Timer; +import java.util.TimerTask; +import java.nio.ByteBuffer; +import java.io.IOException; + +import static com.objectcomputing.checkins.services.validate.PermissionsValidation.NOT_AUTHORIZED_MSG; + +@Singleton +public class ReportDataUploadServicesImpl extends TimerTask implements ReportDataUploadServices { + + private class Stored { + public Date timestamp; + public Map data; + + public Stored() { + data = new HashMap(); + } + } + + private static final Logger LOG = LoggerFactory.getLogger(ReportDataUploadServicesImpl.class); + + private final CurrentUserServices currentUserServices; + private final Map storedUploads = new HashMap(); + private final Timer timer = new Timer(); + + public ReportDataUploadServicesImpl( + CurrentUserServices currentUserServices) { + this.currentUserServices = currentUserServices; + + timer.scheduleAtFixedRate(this, new Date(), 10*60*1000); + } + + + @Override + public void store(CompletedFileUpload file) throws IOException { + MemberProfile currentUser = currentUserServices.getCurrentUser(); + boolean isAdmin = currentUserServices.isAdmin(); + validate(!isAdmin, NOT_AUTHORIZED_MSG); + + Stored perUser; + UUID id = currentUser.getId(); + if (storedUploads.containsKey(id)) { + perUser = storedUploads.get(id); + } else { + perUser = new Stored(); + storedUploads.put(id, perUser); + } + perUser.timestamp = new Date(); + perUser.data.put(file.getName(), file.getByteBuffer()); + } + + public ByteBuffer get(String name) { + MemberProfile currentUser = currentUserServices.getCurrentUser(); + boolean isAdmin = currentUserServices.isAdmin(); + validate(!isAdmin, NOT_AUTHORIZED_MSG); + + UUID id = currentUser.getId(); + if (storedUploads.containsKey(id)) { + Stored perUser = storedUploads.get(id); + if (perUser.data.containsKey(name)) { + perUser.timestamp = new Date(); + return perUser.data.get(name); + } + } + throw new BadArgException("Document does not exist"); + } + + @Override + public void run() { + long current = (new Date()).getTime(); + for (Map.Entry entry : storedUploads.entrySet()) { + Stored value = entry.getValue(); + if (current >= (value.timestamp.getTime() + 60*60*1000)) { + storedUploads.remove(entry.getKey()); + } + } + } + + private void validate(boolean isError, String message, Object... args) { + if (isError) { + throw new BadArgException(String.format(message, args)); + } + } + +} From de62272f453432071d553b9f31dc3b5c8f321491 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Thu, 15 Aug 2024 07:44:06 -0500 Subject: [PATCH 02/55] Initial pass at collecting the report data in preparation for report generation. --- .../services/reports/CompensationHistory.java | 65 ++++++++++++++ .../services/reports/ReportDataCollation.java | 90 +++++++++++++++++++ .../reports/ReportDataUploadServices.java | 5 ++ .../reports/ReportDataUploadServicesImpl.java | 19 +++- 4 files changed, 175 insertions(+), 4 deletions(-) create mode 100644 server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java create mode 100644 server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java new file mode 100644 index 000000000..6b23fde66 --- /dev/null +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java @@ -0,0 +1,65 @@ +package com.objectcomputing.checkins.services.reports; + +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; + +import java.nio.ByteBuffer; +import java.io.ByteArrayInputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +public class CompensationHistory { + + public class Compensation { + private LocalDate startDate; + private float amount; + + public Compensation(LocalDate startDate, float amount) { + this.startDate = startDate; + this.amount = amount; + } + + public LocalDate getStartDate() { + return startDate; + } + + public float getAmount() { + return amount; + } + } + + private List history = new ArrayList(); + + public CompensationHistory() { + } + + public void load(ByteBuffer dataSource) throws IOException { + ByteArrayInputStream stream = new ByteArrayInputStream(dataSource.array()); + InputStreamReader input = new InputStreamReader(stream); + CSVParser csvParser = CSVFormat.RFC4180 + .builder() + .setHeader().setSkipHeaderRecord(true) + .setIgnoreSurroundingSpaces(true) + .setNullString("") + .build() + .parse(input); + + history.clear(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M/d/yyyy"); + for (CSVRecord csvRecord : csvParser) { + Compensation comp = new Compensation( + LocalDate.parse(csvRecord.get("startDate"), formatter), + Float.parseFloat(csvRecord.get("compensation"))); + history.add(comp); + } + } + + public List getHistory() { + return history; + } +} diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java new file mode 100644 index 000000000..b3a24e63f --- /dev/null +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java @@ -0,0 +1,90 @@ +package com.objectcomputing.checkins.services.reports; + +import com.objectcomputing.checkins.exceptions.NotFoundException; +import com.objectcomputing.checkins.services.kudos.kudos_recipient.KudosRecipientRepository; +import com.objectcomputing.checkins.services.kudos.kudos_recipient.KudosRecipient; +import com.objectcomputing.checkins.services.kudos.KudosRepository; +import com.objectcomputing.checkins.services.kudos.Kudos; + +import com.objectcomputing.checkins.services.memberprofile.MemberProfile; +import com.objectcomputing.checkins.services.memberprofile.MemberProfileRepository; + +import java.util.UUID; +import java.util.List; +import java.util.ArrayList; +import java.time.LocalDate; +import java.nio.ByteBuffer; + +public class ReportDataCollation { + private UUID memberId; + private LocalDate startDate; + private LocalDate endDate; + private CompensationHistory compensationHistory; + private KudosRepository kudosRepository; + private KudosRecipientRepository kudosRecipientRepository; + private MemberProfileRepository memberProfileRepository; + private ReportDataUploadServices reportDataUploadServices; + + public ReportDataCollation( + UUID memberId, + LocalDate startDate, LocalDate endDate, + KudosRepository kudosRepository, + KudosRecipientRepository kudosRecipientRepository, + MemberProfileRepository memberProfileRepository, + ReportDataUploadServices reportDataUploadServices) { + this.memberId = memberId; + this.startDate = startDate; + this.endDate = endDate; + this.compensationHistory = new CompensationHistory(); + this.kudosRepository = kudosRepository; + this.kudosRecipientRepository = kudosRecipientRepository; + this.memberProfileRepository = memberProfileRepository; + this.reportDataUploadServices = reportDataUploadServices; + } + + LocalDate getStartDate() { + return startDate; + } + + LocalDate getEndDate() { + return endDate; + } + + /// Get the kudos given to the member during the start and end date range. + List getKudos() { + List recipients = kudosRecipientRepository.findByMemberId(memberId); + List kudosList = new ArrayList(); + for (KudosRecipient recipient : recipients) { + Kudos kudos = kudosRepository.findById(recipient.getId()) + .orElse(null); + if (kudos != null) { + LocalDate created = kudos.getDateCreated(); + if ((created.isEqual(startDate) || + created.isAfter(startDate)) && created.isBefore(endDate)) { + kudosList.add(kudos); + } + } + } + return kudosList; + } + + /// Get the member name, title, and start date among others. + MemberProfile getProfile() { + return memberProfileRepository.findById(memberId).orElseThrow(() -> + new NotFoundException("Member not found") + ); + } + + List getCompensationHistory() { + List history = compensationHistory.getHistory(); + if (history.isEmpty()) { + try { + ByteBuffer buffer = reportDataUploadServices.get("TODO"); + compensationHistory.load(buffer); + history = compensationHistory.getHistory(); + } catch(Exception ex) { + } + } + return history; + } +} diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServices.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServices.java index 4bf382408..9b0ca031d 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServices.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServices.java @@ -1,10 +1,15 @@ package com.objectcomputing.checkins.services.reports; +import com.objectcomputing.checkins.exceptions.NotFoundException; + import io.micronaut.http.multipart.CompletedFileUpload; + +import java.nio.ByteBuffer; import java.io.IOException; public interface ReportDataUploadServices { void store(CompletedFileUpload file) throws IOException; + ByteBuffer get(String name) throws NotFoundException; } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServicesImpl.java index aa8cd089a..d36d85597 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServicesImpl.java @@ -1,6 +1,7 @@ package com.objectcomputing.checkins.services.reports; import com.objectcomputing.checkins.exceptions.BadArgException; +import com.objectcomputing.checkins.exceptions.NotFoundException; import com.objectcomputing.checkins.services.memberprofile.MemberProfile; import com.objectcomputing.checkins.services.memberprofile.MemberProfileRepository; import com.objectcomputing.checkins.services.memberprofile.currentuser.CurrentUserServices; @@ -38,12 +39,14 @@ public Stored() { private final CurrentUserServices currentUserServices; private final Map storedUploads = new HashMap(); private final Timer timer = new Timer(); + private final long expireCheck = 10*60*1000; + private final long expiration = 60*60*1000; public ReportDataUploadServicesImpl( CurrentUserServices currentUserServices) { this.currentUserServices = currentUserServices; - timer.scheduleAtFixedRate(this, new Date(), 10*60*1000); + timer.scheduleAtFixedRate(this, new Date(), expireCheck); } @@ -61,11 +64,15 @@ public void store(CompletedFileUpload file) throws IOException { perUser = new Stored(); storedUploads.put(id, perUser); } + + // Update the timestamp to allow us to check later to see if we + // need to remove this user's data. perUser.timestamp = new Date(); perUser.data.put(file.getName(), file.getByteBuffer()); } - public ByteBuffer get(String name) { + @Override + public ByteBuffer get(String name) throws NotFoundException { MemberProfile currentUser = currentUserServices.getCurrentUser(); boolean isAdmin = currentUserServices.isAdmin(); validate(!isAdmin, NOT_AUTHORIZED_MSG); @@ -74,19 +81,23 @@ public ByteBuffer get(String name) { if (storedUploads.containsKey(id)) { Stored perUser = storedUploads.get(id); if (perUser.data.containsKey(name)) { + // Update the timestamp to allow us to check later to see if we + // need to remove this user's data. perUser.timestamp = new Date(); return perUser.data.get(name); } } - throw new BadArgException("Document does not exist"); + throw new NotFoundException("Document does not exist"); } + /// Check periodically to see if any data has expired. If it has, remove + /// it. @Override public void run() { long current = (new Date()).getTime(); for (Map.Entry entry : storedUploads.entrySet()) { Stored value = entry.getValue(); - if (current >= (value.timestamp.getTime() + 60*60*1000)) { + if (current >= (value.timestamp.getTime() + expiration)) { storedUploads.remove(entry.getKey()); } } From eb07cd3644806c3d40b98260772adb8400b1db86 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Thu, 15 Aug 2024 08:40:23 -0500 Subject: [PATCH 03/55] Attempt to standardize the files to be uploaded and how to access them. --- .../services/reports/ReportDataCollation.java | 3 +- .../reports/ReportDataUploadController.java | 10 ++----- .../reports/ReportDataUploadServices.java | 9 ++++-- .../reports/ReportDataUploadServicesImpl.java | 28 ++++++++++++++++--- 4 files changed, 36 insertions(+), 14 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java index b3a24e63f..96b868bef 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java @@ -79,7 +79,8 @@ List getCompensationHistory() { List history = compensationHistory.getHistory(); if (history.isEmpty()) { try { - ByteBuffer buffer = reportDataUploadServices.get("TODO"); + ByteBuffer buffer = reportDataUploadServices.get(memberId, + ReportDataUploadServices.DataType.compensationHistory); compensationHistory.load(buffer); history = compensationHistory.getHistory(); } catch(Exception ex) { diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadController.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadController.java index e9ee0d07f..763f7637a 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadController.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadController.java @@ -18,6 +18,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.UUID; import java.util.List; import java.io.IOException; @@ -32,18 +33,13 @@ public ReportDataUploadController(ReportDataUploadServices reportDataUploadServi this.reportDataUploadServices = reportDataUploadServices; } - /** - * Parse the CSV file and store it to employee hours table - * @param file - * @{@link HttpResponse} - */ @Post(uri="/upload" , consumes = MediaType.MULTIPART_FORM_DATA) - public Mono> upload(@Part("file") Publisher file){ + public Mono> upload(@Part("memberId") UUID memberId, @Part("file") Publisher file){ return Flux.from(file) .subscribeOn(Schedulers.boundedElastic()) .map(part -> { try { - reportDataUploadServices.store(part); + reportDataUploadServices.store(memberId, part); return part.getFilename(); } catch(IOException ex) { LOG.error(ex.toString()); diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServices.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServices.java index 9b0ca031d..679835fe7 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServices.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServices.java @@ -1,15 +1,20 @@ package com.objectcomputing.checkins.services.reports; +import com.objectcomputing.checkins.exceptions.BadArgException; import com.objectcomputing.checkins.exceptions.NotFoundException; import io.micronaut.http.multipart.CompletedFileUpload; import java.nio.ByteBuffer; import java.io.IOException; +import java.util.UUID; public interface ReportDataUploadServices { + public enum DataType { + compensationHistory, positionHistory, currentInformation + } - void store(CompletedFileUpload file) throws IOException; + void store(UUID memberId, CompletedFileUpload file) throws IOException, BadArgException; - ByteBuffer get(String name) throws NotFoundException; + ByteBuffer get(UUID memberId, DataType dataType) throws NotFoundException; } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServicesImpl.java index d36d85597..2d897a05a 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServicesImpl.java @@ -51,11 +51,12 @@ public ReportDataUploadServicesImpl( @Override - public void store(CompletedFileUpload file) throws IOException { + public void store(UUID memberId, CompletedFileUpload file) throws IOException, BadArgException { MemberProfile currentUser = currentUserServices.getCurrentUser(); boolean isAdmin = currentUserServices.isAdmin(); validate(!isAdmin, NOT_AUTHORIZED_MSG); + // Get the map for the current user. Stored perUser; UUID id = currentUser.getId(); if (storedUploads.containsKey(id)) { @@ -65,14 +66,29 @@ public void store(CompletedFileUpload file) throws IOException { storedUploads.put(id, perUser); } + // Translate the file name to a data type that we know about. + String fileName = file.getName().toLowerCase(); + DataType dataType; + if (fileName.contains("comp")) { + dataType = DataType.compensationHistory; + } else if (fileName.contains("position")) { + dataType = DataType.positionHistory; + } else if (fileName.contains("current") || + fileName.contains("information")) { + dataType = DataType.currentInformation; + } else { + throw new BadArgException("Unable to determine data type: " + fileName); + } + String name = getKeyName(memberId, dataType); + // Update the timestamp to allow us to check later to see if we // need to remove this user's data. perUser.timestamp = new Date(); - perUser.data.put(file.getName(), file.getByteBuffer()); + perUser.data.put(name, file.getByteBuffer()); } @Override - public ByteBuffer get(String name) throws NotFoundException { + public ByteBuffer get(UUID memberId, DataType dataType) throws NotFoundException { MemberProfile currentUser = currentUserServices.getCurrentUser(); boolean isAdmin = currentUserServices.isAdmin(); validate(!isAdmin, NOT_AUTHORIZED_MSG); @@ -80,6 +96,7 @@ public ByteBuffer get(String name) throws NotFoundException { UUID id = currentUser.getId(); if (storedUploads.containsKey(id)) { Stored perUser = storedUploads.get(id); + String name = getKeyName(memberId, dataType); if (perUser.data.containsKey(name)) { // Update the timestamp to allow us to check later to see if we // need to remove this user's data. @@ -103,10 +120,13 @@ public void run() { } } + private String getKeyName(UUID memberId, DataType dataType) { + return memberId.toString() + "-" + dataType.toString(); + } + private void validate(boolean isError, String message, Object... args) { if (isError) { throw new BadArgException(String.format(message, args)); } } - } From 3966af93f77682ce624514335897cdaa6518fae0 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Thu, 15 Aug 2024 10:45:05 -0500 Subject: [PATCH 04/55] Handle the fact that CSV files will contain data for multiple members. --- .../services/reports/CompensationHistory.java | 43 +++++++++++++++---- .../services/reports/ReportDataCollation.java | 8 ++-- .../reports/ReportDataUploadController.java | 5 +-- .../reports/ReportDataUploadServices.java | 5 +-- .../reports/ReportDataUploadServicesImpl.java | 13 ++---- 5 files changed, 47 insertions(+), 27 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java index 6b23fde66..c71161551 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java @@ -1,9 +1,15 @@ package com.objectcomputing.checkins.services.reports; +import com.objectcomputing.checkins.services.memberprofile.MemberProfile; +import com.objectcomputing.checkins.services.memberprofile.MemberProfileRepository; + import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.nio.ByteBuffer; import java.io.ByteArrayInputStream; import java.io.InputStreamReader; @@ -12,18 +18,27 @@ import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; +import java.util.UUID; +import java.util.Optional; +import java.util.stream.Collectors; public class CompensationHistory { public class Compensation { + private UUID memberId; private LocalDate startDate; private float amount; - public Compensation(LocalDate startDate, float amount) { + public Compensation(UUID memberId, LocalDate startDate, float amount) { + this.memberId = memberId; this.startDate = startDate; this.amount = amount; } + public UUID getMemberId() { + return memberId; + } + public LocalDate getStartDate() { return startDate; } @@ -33,12 +48,14 @@ public float getAmount() { } } + private static final Logger LOG = LoggerFactory.getLogger(CompensationHistory.class); private List history = new ArrayList(); public CompensationHistory() { } - public void load(ByteBuffer dataSource) throws IOException { + public void load(MemberProfileRepository memberProfileRepository, + ByteBuffer dataSource) throws IOException { ByteArrayInputStream stream = new ByteArrayInputStream(dataSource.array()); InputStreamReader input = new InputStreamReader(stream); CSVParser csvParser = CSVFormat.RFC4180 @@ -52,14 +69,24 @@ public void load(ByteBuffer dataSource) throws IOException { history.clear(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M/d/yyyy"); for (CSVRecord csvRecord : csvParser) { - Compensation comp = new Compensation( - LocalDate.parse(csvRecord.get("startDate"), formatter), - Float.parseFloat(csvRecord.get("compensation"))); - history.add(comp); + String emailAddress = csvRecord.get("emailAddress"); + Optional memberProfile = + memberProfileRepository.findByWorkEmail(emailAddress); + if (memberProfile.isPresent()) { + Compensation comp = new Compensation( + memberProfile.get().getId(), + LocalDate.parse(csvRecord.get("startDate"), formatter), + Float.parseFloat(csvRecord.get("compensation"))); + history.add(comp); + } else { + LOG.error("Unable to find a profile for " + emailAddress); + } } } - public List getHistory() { - return history; + public List getHistory(UUID memberId) { + return history.stream() + .filter(entry -> entry.getMemberId() == memberId) + .collect(Collectors.toList()); } } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java index 96b868bef..c52affa91 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java @@ -76,13 +76,13 @@ MemberProfile getProfile() { } List getCompensationHistory() { - List history = compensationHistory.getHistory(); + List history = compensationHistory.getHistory(memberId); if (history.isEmpty()) { try { - ByteBuffer buffer = reportDataUploadServices.get(memberId, + ByteBuffer buffer = reportDataUploadServices.get( ReportDataUploadServices.DataType.compensationHistory); - compensationHistory.load(buffer); - history = compensationHistory.getHistory(); + compensationHistory.load(memberProfileRepository, buffer); + history = compensationHistory.getHistory(memberId); } catch(Exception ex) { } } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadController.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadController.java index 763f7637a..540d6d019 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadController.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadController.java @@ -18,7 +18,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.UUID; import java.util.List; import java.io.IOException; @@ -34,12 +33,12 @@ public ReportDataUploadController(ReportDataUploadServices reportDataUploadServi } @Post(uri="/upload" , consumes = MediaType.MULTIPART_FORM_DATA) - public Mono> upload(@Part("memberId") UUID memberId, @Part("file") Publisher file){ + public Mono> upload(@Part("file") Publisher file){ return Flux.from(file) .subscribeOn(Schedulers.boundedElastic()) .map(part -> { try { - reportDataUploadServices.store(memberId, part); + reportDataUploadServices.store(part); return part.getFilename(); } catch(IOException ex) { LOG.error(ex.toString()); diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServices.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServices.java index 679835fe7..fe7315d5e 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServices.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServices.java @@ -7,14 +7,13 @@ import java.nio.ByteBuffer; import java.io.IOException; -import java.util.UUID; public interface ReportDataUploadServices { public enum DataType { compensationHistory, positionHistory, currentInformation } - void store(UUID memberId, CompletedFileUpload file) throws IOException, BadArgException; + void store(CompletedFileUpload file) throws IOException, BadArgException; - ByteBuffer get(UUID memberId, DataType dataType) throws NotFoundException; + ByteBuffer get(DataType dataType) throws NotFoundException; } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServicesImpl.java index 2d897a05a..644471fdf 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServicesImpl.java @@ -51,7 +51,7 @@ public ReportDataUploadServicesImpl( @Override - public void store(UUID memberId, CompletedFileUpload file) throws IOException, BadArgException { + public void store(CompletedFileUpload file) throws IOException, BadArgException { MemberProfile currentUser = currentUserServices.getCurrentUser(); boolean isAdmin = currentUserServices.isAdmin(); validate(!isAdmin, NOT_AUTHORIZED_MSG); @@ -79,16 +79,15 @@ public void store(UUID memberId, CompletedFileUpload file) throws IOException, B } else { throw new BadArgException("Unable to determine data type: " + fileName); } - String name = getKeyName(memberId, dataType); // Update the timestamp to allow us to check later to see if we // need to remove this user's data. perUser.timestamp = new Date(); - perUser.data.put(name, file.getByteBuffer()); + perUser.data.put(dataType.name(), file.getByteBuffer()); } @Override - public ByteBuffer get(UUID memberId, DataType dataType) throws NotFoundException { + public ByteBuffer get(DataType dataType) throws NotFoundException { MemberProfile currentUser = currentUserServices.getCurrentUser(); boolean isAdmin = currentUserServices.isAdmin(); validate(!isAdmin, NOT_AUTHORIZED_MSG); @@ -96,7 +95,7 @@ public ByteBuffer get(UUID memberId, DataType dataType) throws NotFoundException UUID id = currentUser.getId(); if (storedUploads.containsKey(id)) { Stored perUser = storedUploads.get(id); - String name = getKeyName(memberId, dataType); + String name = dataType.name(); if (perUser.data.containsKey(name)) { // Update the timestamp to allow us to check later to see if we // need to remove this user's data. @@ -120,10 +119,6 @@ public void run() { } } - private String getKeyName(UUID memberId, DataType dataType) { - return memberId.toString() + "-" + dataType.toString(); - } - private void validate(boolean isError, String message, Object... args) { if (isError) { throw new BadArgException(String.format(message, args)); From b35dbd0a51b8f04e2d9a41fb8674775d834be4b9 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Fri, 16 Aug 2024 07:50:24 -0500 Subject: [PATCH 05/55] Initialize timestamp of Stored on construction. --- .../services/reports/ReportDataUploadServicesImpl.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServicesImpl.java index 644471fdf..8bafcd5b9 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServicesImpl.java @@ -30,6 +30,7 @@ private class Stored { public Map data; public Stored() { + timestamp = new Date(); data = new HashMap(); } } @@ -67,7 +68,7 @@ public void store(CompletedFileUpload file) throws IOException, BadArgException } // Translate the file name to a data type that we know about. - String fileName = file.getName().toLowerCase(); + String fileName = file.getFilename().toLowerCase(); DataType dataType; if (fileName.contains("comp")) { dataType = DataType.compensationHistory; @@ -83,6 +84,8 @@ public void store(CompletedFileUpload file) throws IOException, BadArgException // Update the timestamp to allow us to check later to see if we // need to remove this user's data. perUser.timestamp = new Date(); + + // Store the user's data based on the determined data type. perUser.data.put(dataType.name(), file.getByteBuffer()); } From f6d1c21e8e837cb92fa0c69a5504b343f4254a4c Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Fri, 16 Aug 2024 07:51:18 -0500 Subject: [PATCH 06/55] Inital pass at adding an upload button for one of the required .csv files. --- web-ui/src/api/uploadcsv.js | 13 +++ web-ui/src/components/menu/Menu.jsx | 1 + web-ui/src/components/routes/Routes.jsx | 5 + web-ui/src/pages/MeritReportPage.css | 17 ++++ web-ui/src/pages/MeritReportPage.jsx | 129 ++++++++++++++++++++++++ 5 files changed, 165 insertions(+) create mode 100644 web-ui/src/api/uploadcsv.js create mode 100644 web-ui/src/pages/MeritReportPage.css create mode 100644 web-ui/src/pages/MeritReportPage.jsx diff --git a/web-ui/src/api/uploadcsv.js b/web-ui/src/api/uploadcsv.js new file mode 100644 index 000000000..6fcae1a9e --- /dev/null +++ b/web-ui/src/api/uploadcsv.js @@ -0,0 +1,13 @@ +import { resolve } from './api.js'; + +export const uploadFile = async (url, cookie, file) => { + return resolve({ + headers: { + 'X-CSRF-Header': cookie, + Accept: 'application/json' + }, + method: 'POST', + url: url, + body: file + }); +}; diff --git a/web-ui/src/components/menu/Menu.jsx b/web-ui/src/components/menu/Menu.jsx index a23ecc31f..b2d808e85 100644 --- a/web-ui/src/components/menu/Menu.jsx +++ b/web-ui/src/components/menu/Menu.jsx @@ -154,6 +154,7 @@ function Menu({ children }) { links.push(['/team-skills-reports', 'Team Skills']); } + links.push(['/merit-reports', 'Merit Report']); links.push(['/volunteer-reports', 'Volunteering']); return links; diff --git a/web-ui/src/components/routes/Routes.jsx b/web-ui/src/components/routes/Routes.jsx index a80cfe209..a8a89803e 100644 --- a/web-ui/src/components/routes/Routes.jsx +++ b/web-ui/src/components/routes/Routes.jsx @@ -24,6 +24,7 @@ import SettingsPage from '../../pages/SettingsPage'; import SkillReportPage from '../../pages/SkillReportPage'; import TeamSkillReportPage from '../../pages/TeamSkillReportPage'; import TeamsPage from '../../pages/TeamsPage'; +import MeritReportPage from '../../pages/MeritReportPage'; import Users from '../admin/users/Users'; import VolunteerReportPage from '../../pages/VolunteerReportPage'; @@ -160,6 +161,10 @@ export default function Routes() {
+ +
+ +
diff --git a/web-ui/src/pages/MeritReportPage.css b/web-ui/src/pages/MeritReportPage.css new file mode 100644 index 000000000..a7ae8bae1 --- /dev/null +++ b/web-ui/src/pages/MeritReportPage.css @@ -0,0 +1,17 @@ +.merit-report-page { + margin: 1rem 3rem; +} + +.merit-report-page .search-results { + margin: 2rem 0 0 0; +} + +.merit-report-page .search-results .MuiChip-root { + margin: 0.25rem; +} + +@media (max-width: 850px) { + .merit-report-page { + margin: 0.5rem 1rem; + } +} diff --git a/web-ui/src/pages/MeritReportPage.jsx b/web-ui/src/pages/MeritReportPage.jsx new file mode 100644 index 000000000..3dc587bb0 --- /dev/null +++ b/web-ui/src/pages/MeritReportPage.jsx @@ -0,0 +1,129 @@ +import React, { useContext, useRef, useState } from 'react'; + +import { Button } from '@mui/material'; + +import { uploadFile } from '../api/uploadcsv'; +import { UPDATE_TOAST } from '../context/actions'; +import { AppContext } from '../context/AppContext'; +import { + selectCsrfToken, + selectOrderedMemberFirstName, +} from '../context/selectors'; + +import './MeritReportPage.css'; +import MemberSelector from '../components/member_selector/MemberSelector'; +import { useQueryParameters } from '../helpers/query-parameters'; + +const MeritReportPage = () => { + const { state, dispatch } = useContext(AppContext); + + const csrf = selectCsrfToken(state); + const memberProfiles = selectOrderedMemberFirstName(state); + + const [selectedMembers, setSelectedMembers] = useState([]); + const [searchResults, setSearchResults] = useState([]); + const [allSearchResults, setAllSearchResults] = useState([]); + const [editedSearchRequest, setEditedSearchRequest] = useState([]); + const [selectedFile, setSelectedFile] = useState(null); + + const processedQPs = useRef(false); + useQueryParameters( + [ + { + name: 'members', + default: [], + value: selectedMembers, + setter(ids) { + const selectedMembers = ids.map(id => + memberProfiles.find(member => member.id === id) + ); + setSelectedMembers(selectedMembers); + }, + toQP() { + return selectedMembers.map(member => member.id).join(','); + } + } + ], + [memberProfiles], + processedQPs + ); + + const selectedMembersCopy = selectedMembers.map(member => ({ ...member })); + let searchResultsCopy = searchResults.map(result => ({ ...result })); + const filteredResults = searchResultsCopy.filter(result => { + return selectedMembersCopy.some(member => { + return result.name === member.name; + }); + }); + + const onFileSelected = e => { + setSelectedFile(e.target.files[0]); + }; + + const upload = async file => { + if (!file) { + return; + } + let formData = new FormData(); + formData.append('file', file); + let res = await uploadFile("/services/report/data/upload", + csrf, formData); + if (res?.error) { + let error = res?.error?.response?.data?.message; + dispatch({ + type: UPDATE_TOAST, + payload: { + severity: 'error', + toast: error + } + }); + } + const data = res?.payload?.data; + if (data) { + dispatch({ + type: UPDATE_TOAST, + payload: { + severity: 'success', + toast: `File was successfully uploaded` + } + }); + } + }; + + + return ( +
+ + +
+ {selectedFile && ( + + )} +
+ + +
+ ); +}; + +export default MeritReportPage; From 57792b72899cd542f3845b40e4db7fcfd82cf740 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Fri, 16 Aug 2024 10:18:55 -0500 Subject: [PATCH 07/55] Inital pass at a Report Data DTO. --- .../services/reports/ReportDataCollation.java | 12 +-- .../reports/ReportDataController.java | 80 +++++++++++++++++++ .../services/reports/ReportDataDTO.java | 41 ++++++++++ ...dServices.java => ReportDataServices.java} | 2 +- ...sImpl.java => ReportDataServicesImpl.java} | 6 +- .../reports/ReportDataUploadController.java | 50 ------------ 6 files changed, 131 insertions(+), 60 deletions(-) create mode 100644 server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java create mode 100644 server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java rename server/src/main/java/com/objectcomputing/checkins/services/reports/{ReportDataUploadServices.java => ReportDataServices.java} (92%) rename server/src/main/java/com/objectcomputing/checkins/services/reports/{ReportDataUploadServicesImpl.java => ReportDataServicesImpl.java} (96%) delete mode 100644 server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadController.java diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java index c52affa91..569c8b3d8 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java @@ -23,7 +23,7 @@ public class ReportDataCollation { private KudosRepository kudosRepository; private KudosRecipientRepository kudosRecipientRepository; private MemberProfileRepository memberProfileRepository; - private ReportDataUploadServices reportDataUploadServices; + private ReportDataServices reportDataServices; public ReportDataCollation( UUID memberId, @@ -31,7 +31,7 @@ public ReportDataCollation( KudosRepository kudosRepository, KudosRecipientRepository kudosRecipientRepository, MemberProfileRepository memberProfileRepository, - ReportDataUploadServices reportDataUploadServices) { + ReportDataServices reportDataServices) { this.memberId = memberId; this.startDate = startDate; this.endDate = endDate; @@ -39,7 +39,7 @@ public ReportDataCollation( this.kudosRepository = kudosRepository; this.kudosRecipientRepository = kudosRecipientRepository; this.memberProfileRepository = memberProfileRepository; - this.reportDataUploadServices = reportDataUploadServices; + this.reportDataServices = reportDataServices; } LocalDate getStartDate() { @@ -69,7 +69,7 @@ List getKudos() { } /// Get the member name, title, and start date among others. - MemberProfile getProfile() { + MemberProfile getMemberProfile() { return memberProfileRepository.findById(memberId).orElseThrow(() -> new NotFoundException("Member not found") ); @@ -79,8 +79,8 @@ List getCompensationHistory() { List history = compensationHistory.getHistory(memberId); if (history.isEmpty()) { try { - ByteBuffer buffer = reportDataUploadServices.get( - ReportDataUploadServices.DataType.compensationHistory); + ByteBuffer buffer = reportDataServices.get( + ReportDataServices.DataType.compensationHistory); compensationHistory.load(memberProfileRepository, buffer); history = compensationHistory.getHistory(memberId); } catch(Exception ex) { diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java new file mode 100644 index 000000000..5eca67cc0 --- /dev/null +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java @@ -0,0 +1,80 @@ +package com.objectcomputing.checkins.services.reports; + +import com.objectcomputing.checkins.services.kudos.KudosRepository; +import com.objectcomputing.checkins.services.kudos.kudos_recipient.KudosRecipientRepository; +import com.objectcomputing.checkins.services.memberprofile.MemberProfileRepository; +import com.objectcomputing.checkins.exceptions.NotFoundException; +import io.micronaut.http.MediaType; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Post; +import io.micronaut.http.annotation.Get; +import io.micronaut.http.multipart.CompletedFileUpload; +import io.micronaut.scheduling.TaskExecutors; +import io.micronaut.scheduling.annotation.ExecuteOn; +import io.micronaut.security.annotation.Secured; +import io.micronaut.security.rules.SecurityRule; +import io.micronaut.http.annotation.Part; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; +import reactor.core.publisher.Flux; +import reactor.core.scheduler.Schedulers; +import jakarta.validation.constraints.NotNull; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.UUID; +import java.time.LocalDate; +import java.io.IOException; + +@Controller("/services/report/data") +@ExecuteOn(TaskExecutors.BLOCKING) +@Secured(SecurityRule.IS_AUTHENTICATED) +public class ReportDataController { + private static final Logger LOG = LoggerFactory.getLogger(ReportDataController.class); + private final ReportDataServices reportDataServices; + private KudosRepository kudosRepository; + private KudosRecipientRepository kudosRecipientRepository; + private MemberProfileRepository memberProfileRepository; + + public ReportDataController(ReportDataServices reportDataServices, + KudosRepository kudosRepository, + KudosRecipientRepository kudosRecipientRepository, + MemberProfileRepository memberProfileRepository) { + this.reportDataServices = reportDataServices; + this.kudosRepository = kudosRepository; + this.kudosRecipientRepository = kudosRecipientRepository; + this.memberProfileRepository = memberProfileRepository; + } + + @Post(uri="/upload" , consumes = MediaType.MULTIPART_FORM_DATA) + public Mono> upload(@Part("file") Publisher file){ + return Flux.from(file) + .subscribeOn(Schedulers.boundedElastic()) + .map(part -> { + try { + reportDataServices.store(part); + return part.getFilename(); + } catch(IOException ex) { + LOG.error(ex.toString()); + return ""; + } + }) + .collectList(); + } + + @Get("/{memberId,startDate,endDate}") + public ReportDataDTO get(@NotNull UUID memberId, @NotNull LocalDate startDate, @NotNull LocalDate endDate) { + ReportDataCollation data = new ReportDataCollation( + memberId, startDate, endDate, + kudosRepository, + kudosRecipientRepository, + memberProfileRepository, + reportDataServices); + ReportDataDTO dto = new ReportDataDTO(memberId, startDate, endDate, + data.getMemberProfile(), data.getKudos(), + data.getCompensationHistory()); + return dto; + } +} diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java new file mode 100644 index 000000000..77439574d --- /dev/null +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java @@ -0,0 +1,41 @@ +package com.objectcomputing.checkins.services.reports; + +import com.objectcomputing.checkins.services.memberprofile.MemberProfile; +import com.objectcomputing.checkins.services.kudos.Kudos; +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.annotation.Nullable; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +@Getter +@Setter +@AllArgsConstructor +@Introspected +public class ReportDataDTO { + + @NotNull + private UUID memberId; + + @NotNull + private LocalDate startDate; + + @NotNull + private LocalDate endDate; + + @NotNull + private MemberProfile memberProfile; + + @Nullable + private List kudos; + + @Nullable + private List compensationHistory; +} diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServices.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServices.java similarity index 92% rename from server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServices.java rename to server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServices.java index fe7315d5e..84149c825 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServices.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServices.java @@ -8,7 +8,7 @@ import java.nio.ByteBuffer; import java.io.IOException; -public interface ReportDataUploadServices { +public interface ReportDataServices { public enum DataType { compensationHistory, positionHistory, currentInformation } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java similarity index 96% rename from server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServicesImpl.java rename to server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java index 8bafcd5b9..8deb4494e 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java @@ -23,7 +23,7 @@ import static com.objectcomputing.checkins.services.validate.PermissionsValidation.NOT_AUTHORIZED_MSG; @Singleton -public class ReportDataUploadServicesImpl extends TimerTask implements ReportDataUploadServices { +public class ReportDataServicesImpl extends TimerTask implements ReportDataServices { private class Stored { public Date timestamp; @@ -35,7 +35,7 @@ public Stored() { } } - private static final Logger LOG = LoggerFactory.getLogger(ReportDataUploadServicesImpl.class); + private static final Logger LOG = LoggerFactory.getLogger(ReportDataServicesImpl.class); private final CurrentUserServices currentUserServices; private final Map storedUploads = new HashMap(); @@ -43,7 +43,7 @@ public Stored() { private final long expireCheck = 10*60*1000; private final long expiration = 60*60*1000; - public ReportDataUploadServicesImpl( + public ReportDataServicesImpl( CurrentUserServices currentUserServices) { this.currentUserServices = currentUserServices; diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadController.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadController.java deleted file mode 100644 index 540d6d019..000000000 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataUploadController.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.objectcomputing.checkins.services.reports; - -import com.objectcomputing.checkins.exceptions.NotFoundException; -import io.micronaut.http.MediaType; -import io.micronaut.http.annotation.Controller; -import io.micronaut.http.annotation.Post; -import io.micronaut.http.multipart.CompletedFileUpload; -import io.micronaut.scheduling.TaskExecutors; -import io.micronaut.scheduling.annotation.ExecuteOn; -import io.micronaut.security.annotation.Secured; -import io.micronaut.security.rules.SecurityRule; -import io.micronaut.http.annotation.Part; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Mono; -import reactor.core.publisher.Flux; -import reactor.core.scheduler.Schedulers; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.List; -import java.io.IOException; - -@Controller("/services/report/data") -@ExecuteOn(TaskExecutors.BLOCKING) -@Secured(SecurityRule.IS_AUTHENTICATED) -public class ReportDataUploadController { - private static final Logger LOG = LoggerFactory.getLogger(ReportDataUploadController.class); - private final ReportDataUploadServices reportDataUploadServices; - - public ReportDataUploadController(ReportDataUploadServices reportDataUploadServices) { - this.reportDataUploadServices = reportDataUploadServices; - } - - @Post(uri="/upload" , consumes = MediaType.MULTIPART_FORM_DATA) - public Mono> upload(@Part("file") Publisher file){ - return Flux.from(file) - .subscribeOn(Schedulers.boundedElastic()) - .map(part -> { - try { - reportDataUploadServices.store(part); - return part.getFilename(); - } catch(IOException ex) { - LOG.error(ex.toString()); - return ""; - } - }) - .collectList(); - } -} From 97da0fbe7d98400ef0d14cf848718446572dbf27 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Fri, 16 Aug 2024 13:01:08 -0500 Subject: [PATCH 08/55] Attempt at getting ReportDataDTO back from the ReportDataController. --- web-ui/src/api/generic.js | 31 ++++++++++++++++++++++++++++ web-ui/src/api/uploadcsv.js | 13 ------------ web-ui/src/pages/MeritReportPage.jsx | 31 +++++++++++++++++++++++++++- 3 files changed, 61 insertions(+), 14 deletions(-) create mode 100644 web-ui/src/api/generic.js delete mode 100644 web-ui/src/api/uploadcsv.js diff --git a/web-ui/src/api/generic.js b/web-ui/src/api/generic.js new file mode 100644 index 000000000..c5763d825 --- /dev/null +++ b/web-ui/src/api/generic.js @@ -0,0 +1,31 @@ +import { resolve } from './api.js'; + +export const uploadFile = async (url, cookie, file) => { + return resolve({ + headers: { + 'X-CSRF-Header': cookie, + Accept: 'application/json' + }, + method: 'POST', + url: url, + body: file + }); +}; + +export const downloadJson = async (url, cookie, params) => { + let fullURL = url; + let separator = '?'; + for(const [key, value] of Object.entries(params)) { + fullURL += separator + key + '=' + value; + separator = '&'; + } + return resolve({ + method: 'GET', + headers: { + 'X-CSRF-Header': cookie, + Accept: 'application/json', + 'Content-Type': 'application/json;charset=UTF-8' + }, + url: fullURL + }); +}; diff --git a/web-ui/src/api/uploadcsv.js b/web-ui/src/api/uploadcsv.js deleted file mode 100644 index 6fcae1a9e..000000000 --- a/web-ui/src/api/uploadcsv.js +++ /dev/null @@ -1,13 +0,0 @@ -import { resolve } from './api.js'; - -export const uploadFile = async (url, cookie, file) => { - return resolve({ - headers: { - 'X-CSRF-Header': cookie, - Accept: 'application/json' - }, - method: 'POST', - url: url, - body: file - }); -}; diff --git a/web-ui/src/pages/MeritReportPage.jsx b/web-ui/src/pages/MeritReportPage.jsx index 3dc587bb0..bb281cf4e 100644 --- a/web-ui/src/pages/MeritReportPage.jsx +++ b/web-ui/src/pages/MeritReportPage.jsx @@ -2,7 +2,7 @@ import React, { useContext, useRef, useState } from 'react'; import { Button } from '@mui/material'; -import { uploadFile } from '../api/uploadcsv'; +import { uploadFile, downloadJson } from '../api/generic'; import { UPDATE_TOAST } from '../context/actions'; import { AppContext } from '../context/AppContext'; import { @@ -90,6 +90,29 @@ const MeritReportPage = () => { } }; + const download = async () => { + for(let selected of selectedMembers) { + console.log("Download for " + selected.id); + let res = await downloadJson("/services/report/data", + csrf, {memberId: selected.id, + startDate: '2024-01-01', + endDate: '2024-12-31'}); + if (res?.error) { + let error = res?.error?.response?.data?.message; + dispatch({ + type: UPDATE_TOAST, + payload: { + severity: 'error', + toast: error + } + }); + } + const data = res?.payload?.data; + if (data) { + console.log(data); + } + } + } return (
@@ -122,6 +145,12 @@ const MeritReportPage = () => { onChange={setSelectedMembers} selected={selectedMembers} /> +
); }; From 738de8fc8635c0fe5e99ff8a6501c7475c8dd46d Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Fri, 16 Aug 2024 13:59:55 -0500 Subject: [PATCH 09/55] Indicate that the get should produce json. --- .../checkins/services/reports/ReportDataController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java index 5eca67cc0..87d4d29ad 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java @@ -48,8 +48,8 @@ public ReportDataController(ReportDataServices reportDataServices, this.memberProfileRepository = memberProfileRepository; } - @Post(uri="/upload" , consumes = MediaType.MULTIPART_FORM_DATA) - public Mono> upload(@Part("file") Publisher file){ + @Post(uri="/upload", consumes = MediaType.MULTIPART_FORM_DATA) + public Mono> upload(@Part("file") Publisher file) { return Flux.from(file) .subscribeOn(Schedulers.boundedElastic()) .map(part -> { @@ -64,7 +64,7 @@ public Mono> upload(@Part("file") Publisher fi .collectList(); } - @Get("/{memberId,startDate,endDate}") + @Get(value = "/{memberId,startDate,endDate}", produces = MediaType.APPLICATION_JSON) public ReportDataDTO get(@NotNull UUID memberId, @NotNull LocalDate startDate, @NotNull LocalDate endDate) { ReportDataCollation data = new ReportDataCollation( memberId, startDate, endDate, From 94abf569b5109e9d1ff1497934bdb7ff6950a2b4 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Mon, 19 Aug 2024 12:12:12 -0500 Subject: [PATCH 10/55] With these fixes, we can actually receive report data. --- .../checkins/services/reports/CompensationHistory.java | 2 +- .../checkins/services/reports/ReportDataCollation.java | 2 +- .../checkins/services/reports/ReportDataController.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java index c71161551..92a82b417 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java @@ -86,7 +86,7 @@ public void load(MemberProfileRepository memberProfileRepository, public List getHistory(UUID memberId) { return history.stream() - .filter(entry -> entry.getMemberId() == memberId) + .filter(entry -> entry.getMemberId().equals(memberId)) .collect(Collectors.toList()); } } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java index 569c8b3d8..bc6c00b21 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java @@ -55,7 +55,7 @@ List getKudos() { List recipients = kudosRecipientRepository.findByMemberId(memberId); List kudosList = new ArrayList(); for (KudosRecipient recipient : recipients) { - Kudos kudos = kudosRepository.findById(recipient.getId()) + Kudos kudos = kudosRepository.findById(recipient.getKudosId()) .orElse(null); if (kudos != null) { LocalDate created = kudos.getDateCreated(); diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java index 87d4d29ad..8fcc69e87 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java @@ -64,7 +64,7 @@ public Mono> upload(@Part("file") Publisher fi .collectList(); } - @Get(value = "/{memberId,startDate,endDate}", produces = MediaType.APPLICATION_JSON) + @Get public ReportDataDTO get(@NotNull UUID memberId, @NotNull LocalDate startDate, @NotNull LocalDate endDate) { ReportDataCollation data = new ReportDataCollation( memberId, startDate, endDate, From cc74bff18a047bbc884fa4b1ba2d52ce69c7ad4c Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Mon, 19 Aug 2024 14:08:29 -0500 Subject: [PATCH 11/55] Added "Current Information" and "Position History". --- .../services/reports/CurrentInformation.java | 107 ++++++++++++++++ .../services/reports/PositionHistory.java | 115 ++++++++++++++++++ .../services/reports/ReportDataCollation.java | 32 +++++ .../reports/ReportDataController.java | 4 +- .../services/reports/ReportDataDTO.java | 10 +- 5 files changed, 265 insertions(+), 3 deletions(-) create mode 100644 server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java create mode 100644 server/src/main/java/com/objectcomputing/checkins/services/reports/PositionHistory.java diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java new file mode 100644 index 000000000..854b0e302 --- /dev/null +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java @@ -0,0 +1,107 @@ +package com.objectcomputing.checkins.services.reports; + +import com.objectcomputing.checkins.services.memberprofile.MemberProfile; +import com.objectcomputing.checkins.services.memberprofile.MemberProfileRepository; + +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; +import java.io.ByteArrayInputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.Optional; +import java.util.stream.Collectors; + +public class CurrentInformation { + + public class Information { + private UUID memberId; + private float salary; + private String range; + private String biography; + private String commitments; + + public Information(UUID memberId, float salary, String range, String biography, String commitments) { + this.memberId = memberId; + this.salary = salary; + this.range = range; + this.biography = biography; + this.commitments = commitments; + } + + public UUID getMemberId() { + return memberId; + } + + public float getSalary() { + return salary; + } + + public String getRange() { + return range; + } + + public String getBiography() { + return biography; + } + + public String getCommitments() { + return commitments; + } + } + + private static final Logger LOG = LoggerFactory.getLogger(CurrentInformation.class); + private List information = new ArrayList(); + + public CurrentInformation() { + } + + public void load(MemberProfileRepository memberProfileRepository, + ByteBuffer dataSource) throws IOException { + ByteArrayInputStream stream = new ByteArrayInputStream(dataSource.array()); + InputStreamReader input = new InputStreamReader(stream); + CSVParser csvParser = CSVFormat.RFC4180 + .builder() + .setHeader().setSkipHeaderRecord(true) + .setIgnoreSurroundingSpaces(true) + .setNullString("") + .build() + .parse(input); + + information.clear(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M/d/yyyy"); + for (CSVRecord csvRecord : csvParser) { + String emailAddress = csvRecord.get("emailAddress"); + Optional memberProfile = + memberProfileRepository.findByWorkEmail(emailAddress); + if (memberProfile.isPresent()) { + Information comp = new Information( + memberProfile.get().getId(), + Float.parseFloat(csvRecord.get("salary")), + csvRecord.get("range"), + csvRecord.get("biography"), + csvRecord.get("commitments") + ); + information.add(comp); + } else { + LOG.error("Unable to find a profile for " + emailAddress); + } + } + } + + public List getInformation(UUID memberId) { + return information.stream() + .filter(entry -> entry.getMemberId().equals(memberId)) + .collect(Collectors.toList()); + } +} diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/PositionHistory.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/PositionHistory.java new file mode 100644 index 000000000..bd6736f1f --- /dev/null +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/PositionHistory.java @@ -0,0 +1,115 @@ +package com.objectcomputing.checkins.services.reports; + +import com.objectcomputing.checkins.services.memberprofile.MemberProfile; +import com.objectcomputing.checkins.services.memberprofile.MemberProfileRepository; + +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; +import java.io.ByteArrayInputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.temporal.ChronoField; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.Optional; +import java.util.stream.Collectors; +import java.time.format.DateTimeParseException; + +public class PositionHistory { + + public class Position { + private UUID memberId; + private LocalDate date; + private String title; + + public Position(UUID memberId, LocalDate date, String title) { + this.memberId = memberId; + this.date = date; + this.title = title; + } + + public UUID getMemberId() { + return memberId; + } + + public LocalDate getDate() { + return date; + } + + public String getTitle() { + return title; + } + } + + private static final Logger LOG = LoggerFactory.getLogger(PositionHistory.class); + private List history = new ArrayList(); + + public PositionHistory() { + } + + public void load(MemberProfileRepository memberProfileRepository, + ByteBuffer dataSource) throws IOException { + ByteArrayInputStream stream = new ByteArrayInputStream(dataSource.array()); + InputStreamReader input = new InputStreamReader(stream); + CSVParser csvParser = CSVFormat.RFC4180 + .builder() + .setHeader().setSkipHeaderRecord(true) + .setIgnoreSurroundingSpaces(true) + .setNullString("") + .build() + .parse(input); + + history.clear(); + for (CSVRecord csvRecord : csvParser) { + String emailAddress = csvRecord.get("emailAddress"); + Optional memberProfile = + memberProfileRepository.findByWorkEmail(emailAddress); + if (memberProfile.isPresent()) { + LocalDate date = parseDate(csvRecord.get("date")); + if (date == null) { + LOG.error("Unable to parse date: " + csvRecord.get("date")); + } else { + Position position = new Position( + memberProfile.get().getId(), + date, + csvRecord.get("title")); + history.add(position); + } + } else { + LOG.error("Unable to find a profile for " + emailAddress); + } + } + } + + private LocalDate parseDate(String date) { + List formatStrings = List.of("yyyy", "M/d/yyyy"); + for(String format: formatStrings) { + try { + return LocalDate.parse(date, + new DateTimeFormatterBuilder() + .appendPattern(format) + .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1) + .parseDefaulting(ChronoField.DAY_OF_MONTH, 1) + .toFormatter()); + } catch(DateTimeParseException ex) { + } + } + return null; + } + + public List getHistory(UUID memberId) { + return history.stream() + .filter(entry -> entry.getMemberId().equals(memberId)) + .collect(Collectors.toList()); + } +} diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java index bc6c00b21..f8a75ce53 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java @@ -20,6 +20,8 @@ public class ReportDataCollation { private LocalDate startDate; private LocalDate endDate; private CompensationHistory compensationHistory; + private CurrentInformation currentInformation; + private PositionHistory positionHistory; private KudosRepository kudosRepository; private KudosRecipientRepository kudosRecipientRepository; private MemberProfileRepository memberProfileRepository; @@ -36,6 +38,8 @@ public ReportDataCollation( this.startDate = startDate; this.endDate = endDate; this.compensationHistory = new CompensationHistory(); + this.currentInformation = new CurrentInformation(); + this.positionHistory = new PositionHistory(); this.kudosRepository = kudosRepository; this.kudosRecipientRepository = kudosRecipientRepository; this.memberProfileRepository = memberProfileRepository; @@ -88,4 +92,32 @@ List getCompensationHistory() { } return history; } + + List getCurrentInformation() { + List information = currentInformation.getInformation(memberId); + if (information.isEmpty()) { + try { + ByteBuffer buffer = reportDataServices.get( + ReportDataServices.DataType.currentInformation); + currentInformation.load(memberProfileRepository, buffer); + information = currentInformation.getInformation(memberId); + } catch(Exception ex) { + } + } + return information; + } + + List getPositionHistory() { + List history = positionHistory.getHistory(memberId); + if (history.isEmpty()) { + try { + ByteBuffer buffer = reportDataServices.get( + ReportDataServices.DataType.positionHistory); + positionHistory.load(memberProfileRepository, buffer); + history = positionHistory.getHistory(memberId); + } catch(Exception ex) { + } + } + return history; + } } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java index 8fcc69e87..9b33eb50b 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java @@ -74,7 +74,9 @@ public ReportDataDTO get(@NotNull UUID memberId, @NotNull LocalDate startDate, @ reportDataServices); ReportDataDTO dto = new ReportDataDTO(memberId, startDate, endDate, data.getMemberProfile(), data.getKudos(), - data.getCompensationHistory()); + data.getCompensationHistory(), + data.getCurrentInformation(), + data.getPositionHistory()); return dto; } } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java index 77439574d..252a2ed58 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java @@ -33,9 +33,15 @@ public class ReportDataDTO { @NotNull private MemberProfile memberProfile; - @Nullable + @NotNull private List kudos; - @Nullable + @NotNull private List compensationHistory; + + @NotNull + private List currentInformation; + + @NotNull + private List positionHistory; } From 771a795ab53b7dc7e93e7920c6fe4de1058c15ca Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Mon, 19 Aug 2024 14:30:09 -0500 Subject: [PATCH 12/55] Changes to allow me to test report data generation. --- web-ui/src/api/generic.js | 14 +++------- web-ui/src/pages/MeritReportPage.jsx | 41 ++++++++++++++-------------- 2 files changed, 24 insertions(+), 31 deletions(-) diff --git a/web-ui/src/api/generic.js b/web-ui/src/api/generic.js index c5763d825..85bc63bc2 100644 --- a/web-ui/src/api/generic.js +++ b/web-ui/src/api/generic.js @@ -1,6 +1,6 @@ import { resolve } from './api.js'; -export const uploadFile = async (url, cookie, file) => { +export const uploadData = (url, cookie, file) => { return resolve({ headers: { 'X-CSRF-Header': cookie, @@ -12,20 +12,14 @@ export const uploadFile = async (url, cookie, file) => { }); }; -export const downloadJson = async (url, cookie, params) => { - let fullURL = url; - let separator = '?'; - for(const [key, value] of Object.entries(params)) { - fullURL += separator + key + '=' + value; - separator = '&'; - } +export const downloadData = (url, cookie, params) => { return resolve({ method: 'GET', + params: params, headers: { 'X-CSRF-Header': cookie, Accept: 'application/json', - 'Content-Type': 'application/json;charset=UTF-8' }, - url: fullURL + url: url //fullURL }); }; diff --git a/web-ui/src/pages/MeritReportPage.jsx b/web-ui/src/pages/MeritReportPage.jsx index bb281cf4e..6788fb258 100644 --- a/web-ui/src/pages/MeritReportPage.jsx +++ b/web-ui/src/pages/MeritReportPage.jsx @@ -2,7 +2,7 @@ import React, { useContext, useRef, useState } from 'react'; import { Button } from '@mui/material'; -import { uploadFile, downloadJson } from '../api/generic'; +import { uploadData, downloadData } from '../api/generic'; import { UPDATE_TOAST } from '../context/actions'; import { AppContext } from '../context/AppContext'; import { @@ -66,7 +66,7 @@ const MeritReportPage = () => { } let formData = new FormData(); formData.append('file', file); - let res = await uploadFile("/services/report/data/upload", + let res = await uploadData("/services/report/data/upload", csrf, formData); if (res?.error) { let error = res?.error?.response?.data?.message; @@ -92,25 +92,24 @@ const MeritReportPage = () => { const download = async () => { for(let selected of selectedMembers) { - console.log("Download for " + selected.id); - let res = await downloadJson("/services/report/data", - csrf, {memberId: selected.id, - startDate: '2024-01-01', - endDate: '2024-12-31'}); - if (res?.error) { - let error = res?.error?.response?.data?.message; - dispatch({ - type: UPDATE_TOAST, - payload: { - severity: 'error', - toast: error - } - }); - } - const data = res?.payload?.data; - if (data) { - console.log(data); - } + let res = await downloadData("/services/report/data", + csrf, {memberId: selected.id, + startDate: '2024-01-01', + endDate: '2024-12-31'}); + if (res?.error) { + let error = res?.error?.response?.data?.message; + dispatch({ + type: UPDATE_TOAST, + payload: { + severity: 'error', + toast: error + } + }); + } + const data = res?.payload?.data; + if (data) { + console.log(data); + } } } From de94836cf20a76f2d9e00e546f231887c9c87c12 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Tue, 20 Aug 2024 07:51:44 -0500 Subject: [PATCH 13/55] Support requesting report data for multiple members. --- .../reports/ReportDataController.java | 14 ++++--- web-ui/src/pages/MeritReportPage.jsx | 41 ++++++++++--------- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java index 9b33eb50b..5faae035c 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java @@ -24,6 +24,7 @@ import org.slf4j.LoggerFactory; import java.util.List; +import java.util.ArrayList; import java.util.UUID; import java.time.LocalDate; import java.io.IOException; @@ -65,18 +66,21 @@ public Mono> upload(@Part("file") Publisher fi } @Get - public ReportDataDTO get(@NotNull UUID memberId, @NotNull LocalDate startDate, @NotNull LocalDate endDate) { - ReportDataCollation data = new ReportDataCollation( + public List get(@NotNull List memberIds, @NotNull LocalDate startDate, @NotNull LocalDate endDate) { + List list = new ArrayList(); + for (UUID memberId : memberIds) { + ReportDataCollation data = new ReportDataCollation( memberId, startDate, endDate, kudosRepository, kudosRecipientRepository, memberProfileRepository, reportDataServices); - ReportDataDTO dto = new ReportDataDTO(memberId, startDate, endDate, + list.add(new ReportDataDTO(memberId, startDate, endDate, data.getMemberProfile(), data.getKudos(), data.getCompensationHistory(), data.getCurrentInformation(), - data.getPositionHistory()); - return dto; + data.getPositionHistory())); + } + return list; } } diff --git a/web-ui/src/pages/MeritReportPage.jsx b/web-ui/src/pages/MeritReportPage.jsx index 6788fb258..ec33623a0 100644 --- a/web-ui/src/pages/MeritReportPage.jsx +++ b/web-ui/src/pages/MeritReportPage.jsx @@ -91,25 +91,28 @@ const MeritReportPage = () => { }; const download = async () => { - for(let selected of selectedMembers) { - let res = await downloadData("/services/report/data", - csrf, {memberId: selected.id, - startDate: '2024-01-01', - endDate: '2024-12-31'}); - if (res?.error) { - let error = res?.error?.response?.data?.message; - dispatch({ - type: UPDATE_TOAST, - payload: { - severity: 'error', - toast: error - } - }); - } - const data = res?.payload?.data; - if (data) { - console.log(data); - } + let selected = selectedMembers.reduce((result, item) => { + result.push(item.id); + return result; + }, []); + + let res = await downloadData("/services/report/data", + csrf, {memberIds: selected, + startDate: '2024-01-01', + endDate: '2024-12-31'}); + if (res?.error) { + let error = res?.error?.response?.data?.message; + dispatch({ + type: UPDATE_TOAST, + payload: { + severity: 'error', + toast: error + } + }); + } + const data = res?.payload?.data; + if (data) { + console.log(data); } } From 33f012f5fe61520cd35f353b8f76bee6a1e27de9 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Wed, 21 Aug 2024 07:48:09 -0500 Subject: [PATCH 14/55] Switch to using a review period instead of a date range. --- .../services/reports/ReportDataCollation.java | 101 +++++++++--------- .../reports/ReportDataController.java | 20 ++-- .../services/reports/ReportDataDTO.java | 6 +- web-ui/src/pages/MeritReportPage.jsx | 60 +++++++++-- 4 files changed, 118 insertions(+), 69 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java index f8a75ce53..2187eeaa0 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java @@ -8,54 +8,59 @@ import com.objectcomputing.checkins.services.memberprofile.MemberProfile; import com.objectcomputing.checkins.services.memberprofile.MemberProfileRepository; +import com.objectcomputing.checkins.services.reviews.ReviewPeriod; +import com.objectcomputing.checkins.services.reviews.ReviewPeriodServices; import java.util.UUID; import java.util.List; import java.util.ArrayList; import java.time.LocalDate; +import java.time.Month; import java.nio.ByteBuffer; public class ReportDataCollation { + private class LocalDateRange { + public LocalDate start; + public LocalDate end; + public LocalDateRange(LocalDate start, LocalDate end) { + this.start = start; + this.end = end; + } + } + private UUID memberId; - private LocalDate startDate; - private LocalDate endDate; + private UUID reviewPeriodId; private CompensationHistory compensationHistory; private CurrentInformation currentInformation; private PositionHistory positionHistory; private KudosRepository kudosRepository; private KudosRecipientRepository kudosRecipientRepository; private MemberProfileRepository memberProfileRepository; + private ReviewPeriodServices reviewPeriodServices; private ReportDataServices reportDataServices; public ReportDataCollation( - UUID memberId, - LocalDate startDate, LocalDate endDate, + UUID memberId, UUID reviewPeriodId, KudosRepository kudosRepository, KudosRecipientRepository kudosRecipientRepository, MemberProfileRepository memberProfileRepository, + ReviewPeriodServices reviewPeriodServices, ReportDataServices reportDataServices) { this.memberId = memberId; - this.startDate = startDate; - this.endDate = endDate; + this.reviewPeriodId = reviewPeriodId; this.compensationHistory = new CompensationHistory(); this.currentInformation = new CurrentInformation(); this.positionHistory = new PositionHistory(); this.kudosRepository = kudosRepository; this.kudosRecipientRepository = kudosRecipientRepository; this.memberProfileRepository = memberProfileRepository; + this.reviewPeriodServices = reviewPeriodServices; this.reportDataServices = reportDataServices; } - LocalDate getStartDate() { - return startDate; - } - - LocalDate getEndDate() { - return endDate; - } - /// Get the kudos given to the member during the start and end date range. - List getKudos() { + public List getKudos() { + LocalDateRange range = getDateRange(); List recipients = kudosRecipientRepository.findByMemberId(memberId); List kudosList = new ArrayList(); for (KudosRecipient recipient : recipients) { @@ -63,8 +68,9 @@ List getKudos() { .orElse(null); if (kudos != null) { LocalDate created = kudos.getDateCreated(); - if ((created.isEqual(startDate) || - created.isAfter(startDate)) && created.isBefore(endDate)) { + if ((created.isEqual(range.start) || + created.isAfter(range.start)) && + created.isBefore(range.end)) { kudosList.add(kudos); } } @@ -73,51 +79,48 @@ List getKudos() { } /// Get the member name, title, and start date among others. - MemberProfile getMemberProfile() { + public MemberProfile getMemberProfile() { return memberProfileRepository.findById(memberId).orElseThrow(() -> new NotFoundException("Member not found") ); } - List getCompensationHistory() { - List history = compensationHistory.getHistory(memberId); - if (history.isEmpty()) { - try { - ByteBuffer buffer = reportDataServices.get( + public List getCompensationHistory() { + ByteBuffer buffer = reportDataServices.get( ReportDataServices.DataType.compensationHistory); - compensationHistory.load(memberProfileRepository, buffer); - history = compensationHistory.getHistory(memberId); - } catch(Exception ex) { - } - } - return history; + compensationHistory.load(memberProfileRepository, buffer); + return compensationHistory.getHistory(memberId); } - List getCurrentInformation() { - List information = currentInformation.getInformation(memberId); - if (information.isEmpty()) { - try { - ByteBuffer buffer = reportDataServices.get( + public List getCurrentInformation() { + ByteBuffer buffer = reportDataServices.get( ReportDataServices.DataType.currentInformation); - currentInformation.load(memberProfileRepository, buffer); - information = currentInformation.getInformation(memberId); - } catch(Exception ex) { - } - } - return information; + currentInformation.load(memberProfileRepository, buffer); + return currentInformation.getInformation(memberId); } - List getPositionHistory() { - List history = positionHistory.getHistory(memberId); - if (history.isEmpty()) { - try { - ByteBuffer buffer = reportDataServices.get( + public List getPositionHistory() { + ByteBuffer buffer = reportDataServices.get( ReportDataServices.DataType.positionHistory); - positionHistory.load(memberProfileRepository, buffer); - history = positionHistory.getHistory(memberId); - } catch(Exception ex) { + positionHistory.load(memberProfileRepository, buffer); + return positionHistory.getHistory(memberId); + } + + private LocalDateRange getDateRange() { + // Return date range based on reviewPeriodId (defaulting to this year). + LocalDate closeDate = LocalDate.now(); + ReviewPeriod reviewPeriod = reviewPeriodServices.findById(reviewPeriodId); + if (reviewPeriod != null) { + LocalDate date = reviewPeriod.getCloseDate().toLocalDate(); + if (date != null) { + closeDate = date; } } - return history; + + LocalDate startDate = closeDate.withMonth(Month.JANUARY.getValue()) + .withDayOfMonth(1); + LocalDate endDate = closeDate.withMonth(Month.DECEMBER.getValue()) + .withDayOfMonth(31); + return new LocalDateRange(startDate, endDate); } } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java index 5faae035c..81b942da7 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java @@ -3,6 +3,7 @@ import com.objectcomputing.checkins.services.kudos.KudosRepository; import com.objectcomputing.checkins.services.kudos.kudos_recipient.KudosRecipientRepository; import com.objectcomputing.checkins.services.memberprofile.MemberProfileRepository; +import com.objectcomputing.checkins.services.reviews.ReviewPeriodServices; import com.objectcomputing.checkins.exceptions.NotFoundException; import io.micronaut.http.MediaType; import io.micronaut.http.annotation.Controller; @@ -26,7 +27,6 @@ import java.util.List; import java.util.ArrayList; import java.util.UUID; -import java.time.LocalDate; import java.io.IOException; @Controller("/services/report/data") @@ -35,18 +35,21 @@ public class ReportDataController { private static final Logger LOG = LoggerFactory.getLogger(ReportDataController.class); private final ReportDataServices reportDataServices; - private KudosRepository kudosRepository; - private KudosRecipientRepository kudosRecipientRepository; - private MemberProfileRepository memberProfileRepository; + private final KudosRepository kudosRepository; + private final KudosRecipientRepository kudosRecipientRepository; + private final MemberProfileRepository memberProfileRepository; + private final ReviewPeriodServices reviewPeriodServices; public ReportDataController(ReportDataServices reportDataServices, KudosRepository kudosRepository, KudosRecipientRepository kudosRecipientRepository, - MemberProfileRepository memberProfileRepository) { + MemberProfileRepository memberProfileRepository, + ReviewPeriodServices reviewPeriodServices) { this.reportDataServices = reportDataServices; this.kudosRepository = kudosRepository; this.kudosRecipientRepository = kudosRecipientRepository; this.memberProfileRepository = memberProfileRepository; + this.reviewPeriodServices = reviewPeriodServices; } @Post(uri="/upload", consumes = MediaType.MULTIPART_FORM_DATA) @@ -66,16 +69,17 @@ public Mono> upload(@Part("file") Publisher fi } @Get - public List get(@NotNull List memberIds, @NotNull LocalDate startDate, @NotNull LocalDate endDate) { + public List get(@NotNull List memberIds, @NotNull UUID reviewPeriodId) { List list = new ArrayList(); for (UUID memberId : memberIds) { ReportDataCollation data = new ReportDataCollation( - memberId, startDate, endDate, + memberId, reviewPeriodId, kudosRepository, kudosRecipientRepository, memberProfileRepository, + reviewPeriodServices, reportDataServices); - list.add(new ReportDataDTO(memberId, startDate, endDate, + list.add(new ReportDataDTO(memberId, reviewPeriodId, data.getMemberProfile(), data.getKudos(), data.getCompensationHistory(), data.getCurrentInformation(), diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java index 252a2ed58..cda78d82b 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java @@ -10,7 +10,6 @@ import lombok.Getter; import lombok.Setter; -import java.time.LocalDate; import java.util.Collections; import java.util.List; import java.util.UUID; @@ -25,10 +24,7 @@ public class ReportDataDTO { private UUID memberId; @NotNull - private LocalDate startDate; - - @NotNull - private LocalDate endDate; + private UUID reviewPeriodId; @NotNull private MemberProfile memberProfile; diff --git a/web-ui/src/pages/MeritReportPage.jsx b/web-ui/src/pages/MeritReportPage.jsx index ec33623a0..5e0e63eb5 100644 --- a/web-ui/src/pages/MeritReportPage.jsx +++ b/web-ui/src/pages/MeritReportPage.jsx @@ -1,8 +1,9 @@ -import React, { useContext, useRef, useState } from 'react'; +import React, { useContext, useRef, useState, useEffect } from 'react'; -import { Button } from '@mui/material'; +import { Autocomplete, Button, TextField } from '@mui/material'; import { uploadData, downloadData } from '../api/generic'; +import { getReviewPeriods } from '../api/reviewperiods'; import { UPDATE_TOAST } from '../context/actions'; import { AppContext } from '../context/AppContext'; import { @@ -25,6 +26,8 @@ const MeritReportPage = () => { const [allSearchResults, setAllSearchResults] = useState([]); const [editedSearchRequest, setEditedSearchRequest] = useState([]); const [selectedFile, setSelectedFile] = useState(null); + const [reviewPeriodId, setReviewPeriodId] = useState([]); + const [reviewPeriods, setReviewPeriods] = useState([]); const processedQPs = useRef(false); useQueryParameters( @@ -56,6 +59,33 @@ const MeritReportPage = () => { }); }); + useEffect(() => { + const getAllReviewPeriods = async () => { + const res = await getReviewPeriods(csrf); + const data = + res && + res.payload && + res.payload.data && + res.payload.status === 200 && + !res.error + ? res.payload.data + : null; + if (data) { + let periods = data.reduce((result, item) => { + if (item.closeDate) { + result.push({label: item.closeDate, id: item.id}); + } + return result; + }, []); + setReviewPeriods(periods); + } + }; + if (csrf) { + getAllReviewPeriods(); + } + }, [csrf, dispatch]); + + const onFileSelected = e => { setSelectedFile(e.target.files[0]); }; @@ -98,8 +128,7 @@ const MeritReportPage = () => { let res = await downloadData("/services/report/data", csrf, {memberIds: selected, - startDate: '2024-01-01', - endDate: '2024-12-31'}); + reviewPeriodId: reviewPeriodId.id}); if (res?.error) { let error = res?.error?.response?.data?.message; dispatch({ @@ -114,13 +143,17 @@ const MeritReportPage = () => { if (data) { console.log(data); } - } + }; + + const onReviewPeriodChange = (event, newValue) => { + setReviewPeriodId(newValue); + }; return (
)}
- + ( + + )} + /> Date: Wed, 21 Aug 2024 08:16:34 -0500 Subject: [PATCH 15/55] Ignore IOException for now. --- .../services/reports/ReportDataCollation.java | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java index 2187eeaa0..5ef7a223a 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java @@ -17,6 +17,7 @@ import java.time.LocalDate; import java.time.Month; import java.nio.ByteBuffer; +import java.io.IOException; public class ReportDataCollation { private class LocalDateRange { @@ -86,23 +87,32 @@ public MemberProfile getMemberProfile() { } public List getCompensationHistory() { - ByteBuffer buffer = reportDataServices.get( + try { + ByteBuffer buffer = reportDataServices.get( ReportDataServices.DataType.compensationHistory); - compensationHistory.load(memberProfileRepository, buffer); + compensationHistory.load(memberProfileRepository, buffer); + } catch(IOException ex) { + } return compensationHistory.getHistory(memberId); } public List getCurrentInformation() { - ByteBuffer buffer = reportDataServices.get( + try { + ByteBuffer buffer = reportDataServices.get( ReportDataServices.DataType.currentInformation); - currentInformation.load(memberProfileRepository, buffer); + currentInformation.load(memberProfileRepository, buffer); + } catch(IOException ex) { + } return currentInformation.getInformation(memberId); } public List getPositionHistory() { - ByteBuffer buffer = reportDataServices.get( + try { + ByteBuffer buffer = reportDataServices.get( ReportDataServices.DataType.positionHistory); - positionHistory.load(memberProfileRepository, buffer); + positionHistory.load(memberProfileRepository, buffer); + } catch(IOException ex) { + } return positionHistory.getHistory(memberId); } From 622aae8864eb47086c57ce44c857c514b14a061a Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Wed, 21 Aug 2024 13:36:53 -0500 Subject: [PATCH 16/55] Put the review period dates in and only return a single "Current Information" per member. --- .../services/reports/CurrentInformation.java | 11 ++++++++-- .../services/reports/ReportDataCollation.java | 21 ++++++++++++++----- .../reports/ReportDataController.java | 1 + .../services/reports/ReportDataDTO.java | 9 +++++++- 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java index 854b0e302..e8effc466 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java @@ -1,5 +1,6 @@ package com.objectcomputing.checkins.services.reports; +import com.objectcomputing.checkins.exceptions.NotFoundException; import com.objectcomputing.checkins.services.memberprofile.MemberProfile; import com.objectcomputing.checkins.services.memberprofile.MemberProfileRepository; @@ -99,9 +100,15 @@ public void load(MemberProfileRepository memberProfileRepository, } } - public List getInformation(UUID memberId) { - return information.stream() + public Information getInformation(UUID memberId) { + // There should only be one entry per member. + List list = information.stream() .filter(entry -> entry.getMemberId().equals(memberId)) .collect(Collectors.toList()); + if (list.isEmpty()) { + throw new NotFoundException("Information not found: " + memberId); + } else { + return list.get(0); + } } } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java index 5ef7a223a..55aaabb6b 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java @@ -30,6 +30,8 @@ public LocalDateRange(LocalDate start, LocalDate end) { } private UUID memberId; + private LocalDate startDate; + private LocalDate endDate; private UUID reviewPeriodId; private CompensationHistory compensationHistory; private CurrentInformation currentInformation; @@ -57,11 +59,21 @@ public ReportDataCollation( this.memberProfileRepository = memberProfileRepository; this.reviewPeriodServices = reviewPeriodServices; this.reportDataServices = reportDataServices; + LocalDateRange range = getDateRange(); + startDate = range.start; + endDate = range.end; + } + + LocalDate getStartDate() { + return startDate; + } + + LocalDate getEndDate() { + return endDate; } /// Get the kudos given to the member during the start and end date range. public List getKudos() { - LocalDateRange range = getDateRange(); List recipients = kudosRecipientRepository.findByMemberId(memberId); List kudosList = new ArrayList(); for (KudosRecipient recipient : recipients) { @@ -69,9 +81,8 @@ public List getKudos() { .orElse(null); if (kudos != null) { LocalDate created = kudos.getDateCreated(); - if ((created.isEqual(range.start) || - created.isAfter(range.start)) && - created.isBefore(range.end)) { + if ((created.isEqual(startDate) || + created.isAfter(startDate)) && created.isBefore(endDate)) { kudosList.add(kudos); } } @@ -96,7 +107,7 @@ public List getCompensationHistory() { return compensationHistory.getHistory(memberId); } - public List getCurrentInformation() { + public CurrentInformation.Information getCurrentInformation() { try { ByteBuffer buffer = reportDataServices.get( ReportDataServices.DataType.currentInformation); diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java index 81b942da7..aaae26702 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java @@ -80,6 +80,7 @@ public List get(@NotNull List memberIds, @NotNull UUID revi reviewPeriodServices, reportDataServices); list.add(new ReportDataDTO(memberId, reviewPeriodId, + data.getStartDate(), data.getEndDate(), data.getMemberProfile(), data.getKudos(), data.getCompensationHistory(), data.getCurrentInformation(), diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java index cda78d82b..6d6ad3260 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java @@ -10,6 +10,7 @@ import lombok.Getter; import lombok.Setter; +import java.time.LocalDate; import java.util.Collections; import java.util.List; import java.util.UUID; @@ -26,6 +27,12 @@ public class ReportDataDTO { @NotNull private UUID reviewPeriodId; + @NotNull + private LocalDate startDate; + + @NotNull + private LocalDate endDate; + @NotNull private MemberProfile memberProfile; @@ -36,7 +43,7 @@ public class ReportDataDTO { private List compensationHistory; @NotNull - private List currentInformation; + private CurrentInformation.Information currentInformation; @NotNull private List positionHistory; From dd92482fddaf5918fb66c630f880b40a7d2b473c Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Wed, 21 Aug 2024 13:39:20 -0500 Subject: [PATCH 17/55] Generate markdown (currently not stored). --- web-ui/package.json | 1 + web-ui/src/pages/MeritReportPage.css | 4 + web-ui/src/pages/MeritReportPage.jsx | 167 ++++++++++-- web-ui/yarn.lock | 367 ++++++++++++++++++++++++++- 4 files changed, 518 insertions(+), 21 deletions(-) diff --git a/web-ui/package.json b/web-ui/package.json index c11875f28..72fb73c25 100644 --- a/web-ui/package.json +++ b/web-ui/package.json @@ -30,6 +30,7 @@ "isomorphic-fetch": "^3.0.0", "js-file-download": "^0.4.12", "lodash": "^4.17.21", + "markdown-builder": "^0.9.0", "mjml": "^5.0.0-alpha.4", "mjml-browser": "^5.0.0-alpha.4", "qs": "^6.13.0", diff --git a/web-ui/src/pages/MeritReportPage.css b/web-ui/src/pages/MeritReportPage.css index a7ae8bae1..e56dcccd1 100644 --- a/web-ui/src/pages/MeritReportPage.css +++ b/web-ui/src/pages/MeritReportPage.css @@ -2,6 +2,10 @@ margin: 1rem 3rem; } +.merit-report-page .review-period-section { + width: 50%; +} + .merit-report-page .search-results { margin: 2rem 0 0 0; } diff --git a/web-ui/src/pages/MeritReportPage.jsx b/web-ui/src/pages/MeritReportPage.jsx index 5e0e63eb5..2c31a529b 100644 --- a/web-ui/src/pages/MeritReportPage.jsx +++ b/web-ui/src/pages/MeritReportPage.jsx @@ -15,6 +15,8 @@ import './MeritReportPage.css'; import MemberSelector from '../components/member_selector/MemberSelector'; import { useQueryParameters } from '../helpers/query-parameters'; +import markdown from 'markdown-builder'; + const MeritReportPage = () => { const { state, dispatch } = useContext(AppContext); @@ -139,9 +141,135 @@ const MeritReportPage = () => { } }); } - const data = res?.payload?.data; - if (data) { - console.log(data); + return res?.payload?.data; + }; + + const dateFromArray = (parts) => { + return new Date(parts[0], parts[1] - 1, parts[2]); + }; + + const formatDate = (date) => { + // Wed Oct 05 2011 + let str = date.toString().slice(4, 15); + return str.slice(0, 6) + "," + str.slice(6); + }; + + const markdownTitle = (data) => { + const memberProfile = data.memberProfile; + const startDate = dateFromArray(data.startDate); + const endDate = dateFromArray(data.endDate); + let text = markdown.headers.h1(memberProfile.firstName + " " + + memberProfile.lastName); + text += memberProfile.title + "\n"; + text += "Review Period: " + + formatDate(startDate) + " - " + formatDate(endDate) + "\n\n"; + return text; + }; + + const markdownCurrentInformation = (data) => { + const memberProfile = data.memberProfile; + const currentInfo = data.currentInformation; + const startDate = dateFromArray(memberProfile.startDate); + const years = (Date.now() - startDate) / (1000 * 60 * 60 * 24 * 365.25); + let text = markdown.headers.h1("Current Information"); + text += years.toFixed(1) + " years\n\n"; + text += markdown.headers.h2("Biographical Notes"); + text += currentInfo.biography + "\n\n"; + return text; + }; + + const markdownKudos = (data) => { + const kudosList = data.kudos; + let text = markdown.headers.h1("Kudos"); + for (let kudos of kudosList) { + const date = dateFromArray(kudos.dateCreated); + text += kudos.message + "\n"; + text += markdown.emphasis.i("     " + + "Submitted on " + formatDate(date)) + "\n\n"; + } + return text; + }; + + const markdownReviews = (data) => { + // TODO: Add reviews on server side + return ""; + }; + + const markdownFeedback = (data) => { + // TODO: Add feedback on server side + return ""; + }; + + const markdownTitleHistory = (data) => { + const posHistory = data.positionHistory.sort((a, b) => { + for(let i = 0; i < a.length; i++) { + if (a.date[i] != b.date[i]) { + return b.date[i] - a.date[i]; + } + } + return 0; + }); + + let text = markdown.headers.h2("Title History"); + text += markdown.lists.ul(posHistory, + (position) => position.date[0] + " - " + + position.title); + return text; + }; + + const markdownCompensation = (data) => { + const currentInfo = data.currentInformation; + let text = markdown.headers.h2("Compensation and Commitments"); + text += "$" + currentInfo.salary.toFixed(2) + " Base Salary\n"; + text += "OCI Range for role: " + currentInfo.range + "\n\n"; + if (currentInfo.commitments) { + text += "Commitments: " + currentInfo.commitments + "\n"; + } else { + text += "No current bonus commitments\n"; + } + text += "\n"; + return text; + }; + + const markdownCompensationHistory = (data) => { + // Sort them latest to oldest and truncate to the first 3. + const compHistory = data.compensationHistory.sort((a, b) => { + for(let i = 0; i < a.length; i++) { + if (a.startDate[i] != b.startDate[i]) { + return b.startDate[i] - a.startDate[i]; + } + } + return 0; + }).slice(0, 3); + + let text = markdown.headers.h2("Compensation History"); + text += markdown.lists.ul(compHistory, + (comp) => formatDate(dateFromArray(comp.startDate)) + " - " + + "$" + comp.amount.toFixed(2) + " (base)"); + return text; + }; + + const markdownReviewerNotes = (data) => { + let text = markdown.headers.h4("Reviewer Notes"); + return text; + }; + + const createReportMarkdownDocuments = async () => { + const dataSet = await download(); + for (let data of dataSet) { + // Generate markdown + let text = markdownTitle(data); + text += markdownCurrentInformation(data); + text += markdownKudos(data); + text += markdownReviews(data); + text += markdownFeedback(data); + text += markdownTitleHistory(data); + text += markdownCompensation(data); + text += markdownCompensationHistory(data); + text += markdownReviewerNotes(data); + + // TODO: Store the markdown on the google drive. + console.log(text); } }; @@ -163,7 +291,6 @@ const MeritReportPage = () => { /> -
{selectedFile && ( )}
- ( - - )} - /> +
+ ( + + )} + /> +
@@ -338,7 +381,7 @@ const MeritReportPage = () => { color="primary" onClick={() => upload(selectedFile)} > - Upload  {selectedFile.name} + {uploadLabel(selectedFile)} )} From 8d66880ebaf457a0e04d49df4a8587c14745c2fb Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Mon, 2 Sep 2024 11:12:25 -0500 Subject: [PATCH 20/55] Add more information to the exception. --- .../checkins/services/reports/CurrentInformation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java index e8effc466..767125d36 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java @@ -106,7 +106,7 @@ public Information getInformation(UUID memberId) { .filter(entry -> entry.getMemberId().equals(memberId)) .collect(Collectors.toList()); if (list.isEmpty()) { - throw new NotFoundException("Information not found: " + memberId); + throw new NotFoundException("Current Information not found for member: " + memberId); } else { return list.get(0); } From 1fd1a596356a6729a048e1cffdbac1c61b00db39 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Mon, 2 Sep 2024 11:14:08 -0500 Subject: [PATCH 21/55] Friendly format the review period date and added space between elements. --- web-ui/src/pages/MeritReportPage.css | 4 ++++ web-ui/src/pages/MeritReportPage.jsx | 16 +++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/web-ui/src/pages/MeritReportPage.css b/web-ui/src/pages/MeritReportPage.css index 5ccf7e4a3..17cc34675 100644 --- a/web-ui/src/pages/MeritReportPage.css +++ b/web-ui/src/pages/MeritReportPage.css @@ -18,6 +18,10 @@ margin: 0.25rem; } +.merit-report-page .space-between { + margin-bottom: 5px; +} + @media (max-width: 850px) { .merit-report-page { margin: 0.5rem 1rem; diff --git a/web-ui/src/pages/MeritReportPage.jsx b/web-ui/src/pages/MeritReportPage.jsx index cecb0a76a..0e3a3cd82 100644 --- a/web-ui/src/pages/MeritReportPage.jsx +++ b/web-ui/src/pages/MeritReportPage.jsx @@ -75,7 +75,8 @@ const MeritReportPage = () => { if (data) { let periods = data.reduce((result, item) => { if (item.closeDate) { - result.push({label: item.closeDate, id: item.id}); + result.push({label: formatReviewDate(item.closeDate), + id: item.id}); } return result; }, []); @@ -88,6 +89,11 @@ const MeritReportPage = () => { }, [csrf, dispatch]); + const formatReviewDate = (str) => { + const date = new Date(Date.parse(str)); + return formatDate(date); + }; + const onFileSelected = e => { setSelectedFile(e.target.files); }; @@ -362,7 +368,7 @@ const MeritReportPage = () => { return (
- -
+
{selectedFile && (
-
+
Date: Tue, 3 Sep 2024 13:14:07 -0500 Subject: [PATCH 22/55] Initial pass at adding feedback to the report. --- .../services/questions/QuestionServices.java | 2 +- .../checkins/services/reports/Feedback.java | 26 ++++++ .../services/reports/ReportDataCollation.java | 93 ++++++++++++++++++- .../reports/ReportDataController.java | 27 +++++- .../services/reports/ReportDataDTO.java | 3 + web-ui/src/pages/MeritReportPage.jsx | 44 ++++++++- 6 files changed, 189 insertions(+), 6 deletions(-) create mode 100644 server/src/main/java/com/objectcomputing/checkins/services/reports/Feedback.java diff --git a/server/src/main/java/com/objectcomputing/checkins/services/questions/QuestionServices.java b/server/src/main/java/com/objectcomputing/checkins/services/questions/QuestionServices.java index 63be37b58..52a5fcb87 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/questions/QuestionServices.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/questions/QuestionServices.java @@ -6,7 +6,7 @@ public interface QuestionServices { Question saveQuestion(Question question); Set readAllQuestions(); - Question findById(UUID skillId); + Question findById(UUID id); Question update(Question question); Set findByText(String text); Set findByCategoryId(UUID categoryId); diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/Feedback.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/Feedback.java new file mode 100644 index 000000000..b50f00857 --- /dev/null +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/Feedback.java @@ -0,0 +1,26 @@ +package com.objectcomputing.checkins.services.reports; + +import io.micronaut.core.annotation.Introspected; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.List; +import java.time.LocalDate; + +@AllArgsConstructor +@Getter +@Introspected +class Feedback { + @AllArgsConstructor + @Getter + public static class Answer { + private final String memberName; + private final LocalDate submitted; + private final String question; + private final String answer; + } + + private String name; + private List answers; +} + diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java index 55aaabb6b..7374f67d6 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java @@ -1,6 +1,7 @@ package com.objectcomputing.checkins.services.reports; import com.objectcomputing.checkins.exceptions.NotFoundException; +import com.objectcomputing.checkins.services.memberprofile.MemberProfileUtils; import com.objectcomputing.checkins.services.kudos.kudos_recipient.KudosRecipientRepository; import com.objectcomputing.checkins.services.kudos.kudos_recipient.KudosRecipient; import com.objectcomputing.checkins.services.kudos.KudosRepository; @@ -10,16 +11,31 @@ import com.objectcomputing.checkins.services.memberprofile.MemberProfileRepository; import com.objectcomputing.checkins.services.reviews.ReviewPeriod; import com.objectcomputing.checkins.services.reviews.ReviewPeriodServices; +import com.objectcomputing.checkins.services.feedback_template.FeedbackTemplateServices; +import com.objectcomputing.checkins.services.feedback_template.FeedbackTemplate; +import com.objectcomputing.checkins.services.feedback_request.FeedbackRequestServices; +import com.objectcomputing.checkins.services.feedback_request.FeedbackRequest; +import com.objectcomputing.checkins.services.feedback_answer.FeedbackAnswerServices; +import com.objectcomputing.checkins.services.feedback_answer.FeedbackAnswer; +import com.objectcomputing.checkins.services.questions.QuestionServices; +import com.objectcomputing.checkins.services.questions.Question; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.UUID; import java.util.List; import java.util.ArrayList; +import java.util.Map; +import java.util.HashMap; import java.time.LocalDate; import java.time.Month; import java.nio.ByteBuffer; import java.io.IOException; public class ReportDataCollation { + private static final Logger LOG = LoggerFactory.getLogger(ReportDataCollation.class); + private class LocalDateRange { public LocalDate start; public LocalDate end; @@ -41,6 +57,10 @@ public LocalDateRange(LocalDate start, LocalDate end) { private MemberProfileRepository memberProfileRepository; private ReviewPeriodServices reviewPeriodServices; private ReportDataServices reportDataServices; + private FeedbackTemplateServices feedbackTemplateServices; + private FeedbackRequestServices feedbackRequestServices; + private FeedbackAnswerServices feedbackAnswerServices; + private QuestionServices questionServices; public ReportDataCollation( UUID memberId, UUID reviewPeriodId, @@ -48,7 +68,11 @@ public ReportDataCollation( KudosRecipientRepository kudosRecipientRepository, MemberProfileRepository memberProfileRepository, ReviewPeriodServices reviewPeriodServices, - ReportDataServices reportDataServices) { + ReportDataServices reportDataServices, + FeedbackTemplateServices feedbackTemplateServices, + FeedbackRequestServices feedbackRequestServices, + FeedbackAnswerServices feedbackAnswerServices, + QuestionServices questionServices) { this.memberId = memberId; this.reviewPeriodId = reviewPeriodId; this.compensationHistory = new CompensationHistory(); @@ -59,6 +83,10 @@ public ReportDataCollation( this.memberProfileRepository = memberProfileRepository; this.reviewPeriodServices = reviewPeriodServices; this.reportDataServices = reportDataServices; + this.feedbackTemplateServices = feedbackTemplateServices; + this.feedbackRequestServices = feedbackRequestServices; + this.feedbackAnswerServices = feedbackAnswerServices; + this.questionServices = questionServices; LocalDateRange range = getDateRange(); startDate = range.start; endDate = range.end; @@ -127,6 +155,69 @@ public List getPositionHistory() { return positionHistory.getHistory(memberId); } + public List getFeedback() { + List feedback = new ArrayList(); + + // Get the list of requests for the member and review period. + // We will need to cross-reference the templates. + List requests = + feedbackRequestServices.findByValues(null, memberId, null, + null, reviewPeriodId, null, null); + + // Iterate over each request and find the template. See if the template + // can be used for a feedback request. + Map templates = + new HashMap(); + for (FeedbackRequest request: requests) { + if (!templates.containsKey(request.getTemplateId())) { + try { + FeedbackTemplate template = + feedbackTemplateServices.getById(request.getTemplateId()); + if (template.getActive()) { + templates.put(template.getId(), template.getTitle()); + } + } catch(NotFoundException ex) { + LOG.error(ex.toString()); + } + } + } + + // Go through each template, find the request that corresponds to the + // template, find the question and answers and put it all together. + for (UUID templateId : templates.keySet()) { + String templateTitle = templates.get(templateId); + List feedbackAnswers = + new ArrayList(); + for (FeedbackRequest request: requests) { + if (request.getTemplateId().equals(templateId)) { + UUID recipientId = request.getRecipientId(); + MemberProfile recipient = memberProfileRepository.findById( + recipientId).orElse(null); + String recipientName = (recipient == null ? + recipientId.toString() : + MemberProfileUtils.getFullName(recipient)); + List answers = + feedbackAnswerServices.findByValues(null, request.getId()); + for (FeedbackAnswer answer : answers) { + try { + Question question = questionServices.findById( + answer.getQuestionId()); + String questionText = question.getText(); + feedbackAnswers.add( + new Feedback.Answer(recipientName, request.getSubmitDate(), + questionText, answer.getAnswer())); + } catch(NotFoundException ex) { + LOG.error(ex.toString()); + } + } + } + } + feedback.add(new Feedback(templateTitle, feedbackAnswers)); + } + + return feedback; + } + private LocalDateRange getDateRange() { // Return date range based on reviewPeriodId (defaulting to this year). LocalDate closeDate = LocalDate.now(); diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java index aaae26702..4f8b66502 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java @@ -4,6 +4,10 @@ import com.objectcomputing.checkins.services.kudos.kudos_recipient.KudosRecipientRepository; import com.objectcomputing.checkins.services.memberprofile.MemberProfileRepository; import com.objectcomputing.checkins.services.reviews.ReviewPeriodServices; +import com.objectcomputing.checkins.services.feedback_template.FeedbackTemplateServices; +import com.objectcomputing.checkins.services.feedback_request.FeedbackRequestServices; +import com.objectcomputing.checkins.services.feedback_answer.FeedbackAnswerServices; +import com.objectcomputing.checkins.services.questions.QuestionServices; import com.objectcomputing.checkins.exceptions.NotFoundException; import io.micronaut.http.MediaType; import io.micronaut.http.annotation.Controller; @@ -39,17 +43,29 @@ public class ReportDataController { private final KudosRecipientRepository kudosRecipientRepository; private final MemberProfileRepository memberProfileRepository; private final ReviewPeriodServices reviewPeriodServices; + private final FeedbackTemplateServices feedbackTemplateServices; + private final FeedbackRequestServices feedbackRequestServices; + private final FeedbackAnswerServices feedbackAnswerServices; + private final QuestionServices questionServices; public ReportDataController(ReportDataServices reportDataServices, KudosRepository kudosRepository, KudosRecipientRepository kudosRecipientRepository, MemberProfileRepository memberProfileRepository, - ReviewPeriodServices reviewPeriodServices) { + ReviewPeriodServices reviewPeriodServices, + FeedbackTemplateServices feedbackTemplateServices, + FeedbackRequestServices feedbackRequestServices, + FeedbackAnswerServices feedbackAnswerServices, + QuestionServices questionServices) { this.reportDataServices = reportDataServices; this.kudosRepository = kudosRepository; this.kudosRecipientRepository = kudosRecipientRepository; this.memberProfileRepository = memberProfileRepository; this.reviewPeriodServices = reviewPeriodServices; + this.feedbackTemplateServices = feedbackTemplateServices; + this.feedbackRequestServices = feedbackRequestServices; + this.feedbackAnswerServices = feedbackAnswerServices; + this.questionServices = questionServices; } @Post(uri="/upload", consumes = MediaType.MULTIPART_FORM_DATA) @@ -78,13 +94,18 @@ public List get(@NotNull List memberIds, @NotNull UUID revi kudosRecipientRepository, memberProfileRepository, reviewPeriodServices, - reportDataServices); + reportDataServices, + feedbackTemplateServices, + feedbackRequestServices, + feedbackAnswerServices, + questionServices); list.add(new ReportDataDTO(memberId, reviewPeriodId, data.getStartDate(), data.getEndDate(), data.getMemberProfile(), data.getKudos(), data.getCompensationHistory(), data.getCurrentInformation(), - data.getPositionHistory())); + data.getPositionHistory(), + data.getFeedback())); } return list; } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java index 6d6ad3260..899e5952e 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java @@ -47,4 +47,7 @@ public class ReportDataDTO { @NotNull private List positionHistory; + + @NotNull + private List feedback; } diff --git a/web-ui/src/pages/MeritReportPage.jsx b/web-ui/src/pages/MeritReportPage.jsx index 0e3a3cd82..39f54a278 100644 --- a/web-ui/src/pages/MeritReportPage.jsx +++ b/web-ui/src/pages/MeritReportPage.jsx @@ -277,9 +277,51 @@ const MeritReportPage = () => { return text; }; + const getUniqueMembers = (answers) => { + let members = {}; + for(let answer of answers) { + const key = answer.memberName; + if (!(key in members)) { + // Put in member name and date + members[key] = dateFromArray(answer.submitted); + } + } + return members; + }; + + const getUniqueQuestions = (answers) => { + let questions = {}; + for(let answer of answers) { + const key = answer.question; + if (!(key in questions)) { + // Put in member name and answer + questions[key] = []; + } + questions[key].push([answer.memberName, answer.answer]); + } + return questions; + }; + const markdownFeedback = (data) => { - // TODO: Add feedback on server side and fill in here. let text = markdown.headers.h1("Feedback"); + const feedbackList = data.feedback; + for(let feedback of feedbackList) { + text += markdown.headers.h2("Template: " + feedback.name); + const members = getUniqueMembers(feedback.answers); + for(let member of Object.keys(members)) { + text += member + ": " + formatDate(members[member]) + "\n"; + } + text += "\n"; + + const questions = getUniqueQuestions(feedback.answers); + for(let question of Object.keys(questions)) { + text += markdown.headers.h3(question) + "\n"; + for(let answer of questions[question]) { + text += answer[0] + ": " + answer[1] + "\n"; + } + text += "\n"; + } + } text += "\n"; return text; }; From 1393ac89583e7f21765d01d5124003baa1a6df4e Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Tue, 3 Sep 2024 15:03:19 -0500 Subject: [PATCH 23/55] Use the TemplateQuestionServices for questions to answers, instead of QuestionServices. --- .../services/reports/ReportDataCollation.java | 30 +++++++++++-------- .../reports/ReportDataController.java | 10 +++---- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java index 7374f67d6..45ceaca66 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java @@ -17,8 +17,8 @@ import com.objectcomputing.checkins.services.feedback_request.FeedbackRequest; import com.objectcomputing.checkins.services.feedback_answer.FeedbackAnswerServices; import com.objectcomputing.checkins.services.feedback_answer.FeedbackAnswer; -import com.objectcomputing.checkins.services.questions.QuestionServices; -import com.objectcomputing.checkins.services.questions.Question; +import com.objectcomputing.checkins.services.feedback_template.template_question.TemplateQuestionServices; +import com.objectcomputing.checkins.services.feedback_template.template_question.TemplateQuestion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,7 +60,7 @@ public LocalDateRange(LocalDate start, LocalDate end) { private FeedbackTemplateServices feedbackTemplateServices; private FeedbackRequestServices feedbackRequestServices; private FeedbackAnswerServices feedbackAnswerServices; - private QuestionServices questionServices; + private TemplateQuestionServices templateQuestionServices; public ReportDataCollation( UUID memberId, UUID reviewPeriodId, @@ -72,7 +72,7 @@ public ReportDataCollation( FeedbackTemplateServices feedbackTemplateServices, FeedbackRequestServices feedbackRequestServices, FeedbackAnswerServices feedbackAnswerServices, - QuestionServices questionServices) { + TemplateQuestionServices templateQuestionServices) { this.memberId = memberId; this.reviewPeriodId = reviewPeriodId; this.compensationHistory = new CompensationHistory(); @@ -86,7 +86,7 @@ public ReportDataCollation( this.feedbackTemplateServices = feedbackTemplateServices; this.feedbackRequestServices = feedbackRequestServices; this.feedbackAnswerServices = feedbackAnswerServices; - this.questionServices = questionServices; + this.templateQuestionServices = templateQuestionServices; LocalDateRange range = getDateRange(); startDate = range.start; endDate = range.end; @@ -160,16 +160,18 @@ public List getFeedback() { // Get the list of requests for the member and review period. // We will need to cross-reference the templates. + LocalDateRange dateRange = getDateRange(); List requests = feedbackRequestServices.findByValues(null, memberId, null, - null, reviewPeriodId, null, null); + dateRange.start, null, null, null); // Iterate over each request and find the template. See if the template // can be used for a feedback request. Map templates = new HashMap(); for (FeedbackRequest request: requests) { - if (!templates.containsKey(request.getTemplateId())) { + if (request.getReviewPeriodId() == null && + !templates.containsKey(request.getTemplateId())) { try { FeedbackTemplate template = feedbackTemplateServices.getById(request.getTemplateId()); @@ -199,16 +201,18 @@ public List getFeedback() { List answers = feedbackAnswerServices.findByValues(null, request.getId()); for (FeedbackAnswer answer : answers) { + String questionText; try { - Question question = questionServices.findById( - answer.getQuestionId()); - String questionText = question.getText(); - feedbackAnswers.add( - new Feedback.Answer(recipientName, request.getSubmitDate(), - questionText, answer.getAnswer())); + TemplateQuestion question = + templateQuestionServices.getById(answer.getQuestionId()); + questionText = question.getQuestion(); } catch(NotFoundException ex) { LOG.error(ex.toString()); + questionText = answer.getQuestionId().toString(); } + feedbackAnswers.add( + new Feedback.Answer(recipientName, request.getSubmitDate(), + questionText, answer.getAnswer())); } } } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java index 4f8b66502..fc1e5fdd1 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java @@ -7,7 +7,7 @@ import com.objectcomputing.checkins.services.feedback_template.FeedbackTemplateServices; import com.objectcomputing.checkins.services.feedback_request.FeedbackRequestServices; import com.objectcomputing.checkins.services.feedback_answer.FeedbackAnswerServices; -import com.objectcomputing.checkins.services.questions.QuestionServices; +import com.objectcomputing.checkins.services.feedback_template.template_question.TemplateQuestionServices; import com.objectcomputing.checkins.exceptions.NotFoundException; import io.micronaut.http.MediaType; import io.micronaut.http.annotation.Controller; @@ -46,7 +46,7 @@ public class ReportDataController { private final FeedbackTemplateServices feedbackTemplateServices; private final FeedbackRequestServices feedbackRequestServices; private final FeedbackAnswerServices feedbackAnswerServices; - private final QuestionServices questionServices; + private final TemplateQuestionServices templateQuestionServices; public ReportDataController(ReportDataServices reportDataServices, KudosRepository kudosRepository, @@ -56,7 +56,7 @@ public ReportDataController(ReportDataServices reportDataServices, FeedbackTemplateServices feedbackTemplateServices, FeedbackRequestServices feedbackRequestServices, FeedbackAnswerServices feedbackAnswerServices, - QuestionServices questionServices) { + TemplateQuestionServices templateQuestionServices) { this.reportDataServices = reportDataServices; this.kudosRepository = kudosRepository; this.kudosRecipientRepository = kudosRecipientRepository; @@ -65,7 +65,7 @@ public ReportDataController(ReportDataServices reportDataServices, this.feedbackTemplateServices = feedbackTemplateServices; this.feedbackRequestServices = feedbackRequestServices; this.feedbackAnswerServices = feedbackAnswerServices; - this.questionServices = questionServices; + this.templateQuestionServices = templateQuestionServices; } @Post(uri="/upload", consumes = MediaType.MULTIPART_FORM_DATA) @@ -98,7 +98,7 @@ public List get(@NotNull List memberIds, @NotNull UUID revi feedbackTemplateServices, feedbackRequestServices, feedbackAnswerServices, - questionServices); + templateQuestionServices); list.add(new ReportDataDTO(memberId, reviewPeriodId, data.getStartDate(), data.getEndDate(), data.getMemberProfile(), data.getKudos(), From 7b3b43f8320866f7ef22830171d2c22c97b8c083 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Wed, 4 Sep 2024 09:58:44 -0500 Subject: [PATCH 24/55] Added reviews and self-reviews. --- .../services/reports/ReportDataCollation.java | 47 ++++++++++++++++--- .../reports/ReportDataController.java | 2 + .../services/reports/ReportDataDTO.java | 6 +++ .../reports/ReportDataServicesImpl.java | 3 +- web-ui/src/pages/MeritReportPage.jsx | 38 +++++++++++++-- 5 files changed, 84 insertions(+), 12 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java index 45ceaca66..826fcfecb 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java @@ -45,6 +45,10 @@ public LocalDateRange(LocalDate start, LocalDate end) { } } + private enum FeedbackType { + selfReviews, reviews, feedback + } + private UUID memberId; private LocalDate startDate; private LocalDate endDate; @@ -155,7 +159,19 @@ public List getPositionHistory() { return positionHistory.getHistory(memberId); } + public List getSelfReviews() { + return getFeedbackType(FeedbackType.selfReviews); + } + + public List getReviews() { + return getFeedbackType(FeedbackType.reviews); + } + public List getFeedback() { + return getFeedbackType(FeedbackType.feedback); + } + + private List getFeedbackType(FeedbackType type) { List feedback = new ArrayList(); // Get the list of requests for the member and review period. @@ -163,19 +179,38 @@ public List getFeedback() { LocalDateRange dateRange = getDateRange(); List requests = feedbackRequestServices.findByValues(null, memberId, null, - dateRange.start, null, null, null); + dateRange.start, + type == FeedbackType.feedback ? + null : reviewPeriodId, + null, null); - // Iterate over each request and find the template. See if the template - // can be used for a feedback request. - Map templates = - new HashMap(); + // Iterate over each request and find the template. Determine the purpose + // of the template. + ReviewPeriod reviewPeriod = reviewPeriodServices.findById(reviewPeriodId); + Map templates = new HashMap(); for (FeedbackRequest request: requests) { if (request.getReviewPeriodId() == null && !templates.containsKey(request.getTemplateId())) { try { FeedbackTemplate template = feedbackTemplateServices.getById(request.getTemplateId()); - if (template.getActive()) { + boolean use = true; + switch(type) { + case FeedbackType.selfReviews: + use = template.getIsReview() && + template.getId().equals( + reviewPeriod.getSelfReviewTemplateId()); + break; + case FeedbackType.reviews: + use = template.getIsReview() && + template.getId().equals( + reviewPeriod.getReviewTemplateId()); + break; + case FeedbackType.feedback: + use = !template.getIsReview(); + break; + } + if (use) { templates.put(template.getId(), template.getTitle()); } } catch(NotFoundException ex) { diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java index fc1e5fdd1..8cf77e44f 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java @@ -105,6 +105,8 @@ public List get(@NotNull List memberIds, @NotNull UUID revi data.getCompensationHistory(), data.getCurrentInformation(), data.getPositionHistory(), + data.getSelfReviews(), + data.getReviews(), data.getFeedback())); } return list; diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java index 899e5952e..35c118536 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java @@ -48,6 +48,12 @@ public class ReportDataDTO { @NotNull private List positionHistory; + @NotNull + private List selfReviews; + + @NotNull + private List reviews; + @NotNull private List feedback; } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java index 8deb4494e..77c3b55e1 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java @@ -106,7 +106,8 @@ public ByteBuffer get(DataType dataType) throws NotFoundException { return perUser.data.get(name); } } - throw new NotFoundException("Document does not exist"); + throw new NotFoundException(dataType.toString() + + " Document does not exist"); } /// Check periodically to see if any data has expired. If it has, remove diff --git a/web-ui/src/pages/MeritReportPage.jsx b/web-ui/src/pages/MeritReportPage.jsx index 39f54a278..4881dc44a 100644 --- a/web-ui/src/pages/MeritReportPage.jsx +++ b/web-ui/src/pages/MeritReportPage.jsx @@ -268,13 +268,40 @@ const MeritReportPage = () => { return text; }; - const markdownReviews = (data) => { - // TODO: Add reviews on server side and fill in here. - let text = markdown.headers.h1("Self-Review"); - text += "\n"; - text += markdown.headers.h1("Reviews"); + const markdownReviewsImpl = (title, feedbackList, listMembers) => { + let text = markdown.headers.h1(title); + for(let feedback of feedbackList) { + const members = getUniqueMembers(feedback.answers); + for(let member of Object.keys(members)) { + if (listMembers) { + text += member + ": "; + } + text += "Submitted - " + formatDate(members[member]) + "\n"; + } + text += "\n"; + + const questions = getUniqueQuestions(feedback.answers); + for(let question of Object.keys(questions)) { + text += markdown.headers.h3(question) + "\n"; + for(let answer of questions[question]) { + if (listMembers) { + text += answer[0] + ": "; + } + text += answer[1] + "\n"; + } + text += "\n"; + } + } text += "\n"; return text; + } + + const markdownSelfReviews = (data) => { + return markdownReviewsImpl("Self-Review", data.selfReviews, false); + } + + const markdownReviews = (data) => { + return markdownReviewsImpl("Reviews", data.reviews, true); }; const getUniqueMembers = (answers) => { @@ -389,6 +416,7 @@ const MeritReportPage = () => { let text = markdownTitle(data); text += markdownCurrentInformation(data); text += markdownKudos(data); + text += markdownSelfReviews(data); text += markdownReviews(data); text += markdownFeedback(data); text += markdownTitleHistory(data); From fc9dcc99a4f06217745dd8339c603498d37797f9 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Thu, 5 Sep 2024 09:33:07 -0500 Subject: [PATCH 25/55] Added support for the different types of questions and test data. --- .../checkins/services/reports/Feedback.java | 1 + .../services/reports/ReportDataCollation.java | 35 ++-- .../checkins/services/reports/feedback.txt | 20 +++ .../resources/db/dev/R__Load_testing_data.sql | 156 ++++++++++++++++++ web-ui/src/pages/MeritReportPage.jsx | 52 +++++- 5 files changed, 244 insertions(+), 20 deletions(-) create mode 100644 server/src/main/java/com/objectcomputing/checkins/services/reports/feedback.txt diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/Feedback.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/Feedback.java index b50f00857..0dea90e3e 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/Feedback.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/Feedback.java @@ -18,6 +18,7 @@ public static class Answer { private final LocalDate submitted; private final String question; private final String answer; + private final String type; } private String name; diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java index 826fcfecb..3d040c49f 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java @@ -49,6 +49,8 @@ private enum FeedbackType { selfReviews, reviews, feedback } + private static final String textQuestion = "TEXT"; + private static final String radioQuestion = "RADIO"; private UUID memberId; private LocalDate startDate; private LocalDate endDate; @@ -180,9 +182,7 @@ private List getFeedbackType(FeedbackType type) { List requests = feedbackRequestServices.findByValues(null, memberId, null, dateRange.start, - type == FeedbackType.feedback ? - null : reviewPeriodId, - null, null); + null, null, null); // Iterate over each request and find the template. Determine the purpose // of the template. @@ -237,17 +237,25 @@ private List getFeedbackType(FeedbackType type) { feedbackAnswerServices.findByValues(null, request.getId()); for (FeedbackAnswer answer : answers) { String questionText; + String questionType = textQuestion; try { TemplateQuestion question = templateQuestionServices.getById(answer.getQuestionId()); questionText = question.getQuestion(); + questionType = question.getInputType(); } catch(NotFoundException ex) { LOG.error(ex.toString()); questionText = answer.getQuestionId().toString(); } + feedbackAnswers.add( - new Feedback.Answer(recipientName, request.getSubmitDate(), - questionText, answer.getAnswer())); + new Feedback.Answer( + recipientName, request.getSubmitDate(), questionText, + questionType.equals(textQuestion) || + questionType.equals(radioQuestion) ? + answer.getAnswer() : + String.valueOf(answer.getSentiment()), + questionType)); } } } @@ -258,20 +266,19 @@ private List getFeedbackType(FeedbackType type) { } private LocalDateRange getDateRange() { - // Return date range based on reviewPeriodId (defaulting to this year). + // Return date range based on reviewPeriodId (defaulting to this year). + ReviewPeriod reviewPeriod = reviewPeriodServices.findById(reviewPeriodId); + if (reviewPeriod == null) { LocalDate closeDate = LocalDate.now(); - ReviewPeriod reviewPeriod = reviewPeriodServices.findById(reviewPeriodId); - if (reviewPeriod != null) { - LocalDate date = reviewPeriod.getCloseDate().toLocalDate(); - if (date != null) { - closeDate = date; - } - } - LocalDate startDate = closeDate.withMonth(Month.JANUARY.getValue()) .withDayOfMonth(1); LocalDate endDate = closeDate.withMonth(Month.DECEMBER.getValue()) .withDayOfMonth(31); return new LocalDateRange(startDate, endDate); + } else { + LocalDate startDate = reviewPeriod.getPeriodStartDate().toLocalDate(); + LocalDate endDate = reviewPeriod.getPeriodEndDate().toLocalDate(); + return new LocalDateRange(startDate, endDate); + } } } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/feedback.txt b/server/src/main/java/com/objectcomputing/checkins/services/reports/feedback.txt new file mode 100644 index 000000000..690882a13 --- /dev/null +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/feedback.txt @@ -0,0 +1,20 @@ +FeedbackTemplate + - id <----------------------------------+ + - is_review | + | +FeedbackRequest (one or more) | + - id <-------------------------+ | + - recipientId (supervisor) | | + - requesteeId (target person) | | + - templateId <-----------------C--------+ + - submitDate | + | +FeedbackAnswer | + - id | + - questionId? | + - requestId <------------------+ + - questionId <------+ + | +TemplateQuestion | + - id <--------------+ + - question diff --git a/server/src/main/resources/db/dev/R__Load_testing_data.sql b/server/src/main/resources/db/dev/R__Load_testing_data.sql index af8fd0493..8ce09005d 100644 --- a/server/src/main/resources/db/dev/R__Load_testing_data.sql +++ b/server/src/main/resources/db/dev/R__Load_testing_data.sql @@ -1267,6 +1267,24 @@ INSERT INTO feedback_requests VALUES ('e2af1c96-a593-48c2-b9e0-a00193a070c7', '8d75c07e-6adc-437a-8659-7dd953ce6600', '1c813446-c65a-4f49-b980-0193f7bfff8c', 'dfe2f986-fac0-11eb-9a03-0242ac130003','18ef2032-c264-411e-a8e1-ddda9a714bae', '2021-08-01', '2021-08-05', '2021-08-02', 'submitted'); +-- CAE - Self-Review feedback request, Creator: Big Boss +INSERT INTO feedback_requests +(id, creator_id, requestee_id, recipient_id, template_id, send_date, due_date, submit_date, status) +VALUES +('98390c09-7121-110a-bfee-9380a470a7ef', '72655c4f-1fb8-4514-b31e-7f7e19fa9bd7', 'c7406157-a38f-4d48-aaed-04018d846727', 'c7406157-a38f-4d48-aaed-04018d846727', '926a37a4-4ded-4633-8900-715b0383aecc', '2024-09-04', '2024-09-30', '2024-09-05', 'sent'); + +-- CAE - Review feedback request, Creator: Big Boss +INSERT INTO feedback_requests +(id, creator_id, requestee_id, recipient_id, template_id, send_date, due_date, submit_date, status) +VALUES +('98390c09-7121-110a-bfee-9380a470a7f0', '72655c4f-1fb8-4514-b31e-7f7e19fa9bd7', 'c7406157-a38f-4d48-aaed-04018d846727', '6207b3fd-042d-49aa-9e28-dcc04f537c2d', 'd1e94b60-47c4-4945-87d1-4dc88f088e57', '2024-09-04', '2024-09-30', '2024-09-05', 'sent'); + +-- CAE - Feedback request, Creator: Big Boss +INSERT INTO feedback_requests +(id, creator_id, requestee_id, recipient_id, template_id, send_date, due_date, submit_date, status) +VALUES +('98390c09-7121-110a-bfee-9380a470a7f1', '72655c4f-1fb8-4514-b31e-7f7e19fa9bd7', 'c7406157-a38f-4d48-aaed-04018d846727', '6207b3fd-042d-49aa-9e28-dcc04f537c2d', '1c8bc142-c447-4889-986e-42ab177da683', '2024-09-04', '2024-09-30', '2024-09-05', 'sent'); + ---- Creator: Big Boss INSERT INTO feedback_requests (id, creator_id, requestee_id, recipient_id, template_id, send_date, due_date, submit_date, status) -- requestee: Faux Freddy, recipient: Unreal Ulysses @@ -1391,6 +1409,137 @@ INSERT INTO feedback_answers VALUES ('8c13ffa2-fad0-11eb-9a03-0242ac130003', PGP_SYM_ENCRYPT('They are very good at working on a team--all of us is better than any one of us', '${aeskey}'), 'afa7e2cb-366a-4c16-a205-c0d493b80d85', 'e238dd00-fac4-11eb-9a03-0242ac130003', 0.8); +-- CAE - Self-review 926a37a4-4ded-4633-8900-715b0383aecc answers + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121110', PGP_SYM_ENCRYPT('', '${aeskey}'), 'f4a394de-bcc0-40ad-9b86-3fa7bd6c09fe', '98390c09-7121-110a-bfee-9380a470a7ef', 0.6); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121111', PGP_SYM_ENCRYPT('', '${aeskey}'), '6ea9ea69-62ba-4835-b2b3-43d565df209f', '98390c09-7121-110a-bfee-9380a470a7ef', 0.8); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121112', PGP_SYM_ENCRYPT('', '${aeskey}'), 'aa58579c-3d6c-4238-a3bd-e3f904584f3f', '98390c09-7121-110a-bfee-9380a470a7ef', 0.8); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121113', PGP_SYM_ENCRYPT('', '${aeskey}'), '611536b3-2275-4509-92db-0372bac60aff', '98390c09-7121-110a-bfee-9380a470a7ef', 0.6); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121114', PGP_SYM_ENCRYPT('', '${aeskey}'), '68549286-76f3-49f6-9de6-5f96abfa0b0e', '98390c09-7121-110a-bfee-9380a470a7ef', 0.4); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121115', PGP_SYM_ENCRYPT('', '${aeskey}'), 'd77848f3-cb94-4161-b558-7aa230dcc92c', '98390c09-7121-110a-bfee-9380a470a7ef', 0.8); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121116', PGP_SYM_ENCRYPT('', '${aeskey}'), 'b07d06b6-8d6c-4c39-88f9-b3ccc7a691c4', '98390c09-7121-110a-bfee-9380a470a7ef', 0.8); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121117', PGP_SYM_ENCRYPT('Some text for this question.', '${aeskey}'), '82be76a8-0cf8-427d-9d6a-763c23c05db2', '98390c09-7121-110a-bfee-9380a470a7ef', 0); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121118', PGP_SYM_ENCRYPT('Some text for this question.', '${aeskey}'), 'f9e5878c-6c4d-4249-8f1a-c3508d8c1597', '98390c09-7121-110a-bfee-9380a470a7ef', 0); + +-- CAE - Review d1e94b60-47c4-4945-87d1-4dc88f088e57 answers + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121119', PGP_SYM_ENCRYPT('', '${aeskey}'), '6eff224d-d690-4d25-a44f-08b8ec03fbbe', '98390c09-7121-110a-bfee-9380a470a7f0', 0.4); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121120', PGP_SYM_ENCRYPT('', '${aeskey}'), '47758dfb-64ca-4203-a5ba-b5fa3ef254dd', '98390c09-7121-110a-bfee-9380a470a7f0', .8); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121121', PGP_SYM_ENCRYPT('', '${aeskey}'), '4ab44c4f-bd8f-4e8d-89b7-940ada1a45c0', '98390c09-7121-110a-bfee-9380a470a7f0', .2); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121122', PGP_SYM_ENCRYPT('', '${aeskey}'), '1fe655dc-9c37-4e55-9bd9-455059832531', '98390c09-7121-110a-bfee-9380a470a7f0', .6); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121123', PGP_SYM_ENCRYPT('', '${aeskey}'), 'cf00f422-0b8a-4219-bc3a-8092732f2ef5', '98390c09-7121-110a-bfee-9380a470a7f0', .4); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121124', PGP_SYM_ENCRYPT('', '${aeskey}'), '3f74c7fe-74fe-4a4d-96c3-aaa35d0cbe70', '98390c09-7121-110a-bfee-9380a470a7f0', .6); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121125', PGP_SYM_ENCRYPT('', '${aeskey}'), '40448b19-7d3a-4862-aa97-506e768ca4f9', '98390c09-7121-110a-bfee-9380a470a7f0', .8); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121126', PGP_SYM_ENCRYPT('', '${aeskey}'), '7fad2952-fa1a-43eb-86e6-9a715674c884', '98390c09-7121-110a-bfee-9380a470a7f0', 0); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121127', PGP_SYM_ENCRYPT('Text for this question.', '${aeskey}'), '93424f36-64a3-4f10-b78a-00de58060177', '98390c09-7121-110a-bfee-9380a470a7f0', 0); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121128', PGP_SYM_ENCRYPT('Yes', '${aeskey}'), '8c13c1a5-f1ef-43cc-9f9a-858b01bff930', '98390c09-7121-110a-bfee-9380a470a7f0', 0); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121129', PGP_SYM_ENCRYPT('No', '${aeskey}'), 'c2ff8a0d-358f-438b-86f4-59da10bddbe5', '98390c09-7121-110a-bfee-9380a470a7f0', 0); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121130', PGP_SYM_ENCRYPT('No', '${aeskey}'), '46cf546a-acbe-48e5-8c8d-1b1ca484af8d', '98390c09-7121-110a-bfee-9380a470a7f0', 0); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121131', PGP_SYM_ENCRYPT('Text for the last question.', '${aeskey}'), '174e5851-cb24-4a0f-890c-e6f041db4127', '98390c09-7121-110a-bfee-9380a470a7f0', 0); + +-- CAE - Feedback 1c8bc142-c447-4889-986e-42ab177da683 answers + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121132', PGP_SYM_ENCRYPT('No', '${aeskey}'), '22113310-04dd-4931-96f2-37303a2515a4', '98390c09-7121-110a-bfee-9380a470a7f1', 0); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121133', PGP_SYM_ENCRYPT('', '${aeskey}'), '11d7b14c-2eee-4f72-a2b6-8c57a094207e', '98390c09-7121-110a-bfee-9380a470a7f1', 0.4); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121134', PGP_SYM_ENCRYPT('Feeback answer.', '${aeskey}'), 'bf328e35-e486-4ec8-b3e8-acc2c09419fa', '98390c09-7121-110a-bfee-9380a470a7f1', 0); + -- Skills INSERT INTO skills -- React (id, name, pending, description, extraneous) @@ -1715,3 +1864,10 @@ INSERT INTO role_documentation (role_id, document_id, display_order) VALUES ('d03f5f0b-e29c-4cf4-9ea4-6baa09405c56', 'b553d4c0-9b7a-4691-8fe0-e3bdda4f67ae', 3); + +-- CAE - Review Periods +INSERT INTO review_periods +(id, name, review_status, review_template_id, self_review_template_id, launch_date, self_review_close_date, close_date, period_start_date, period_end_date) +VALUES + ('12345678-e29c-4cf4-9ea4-6baa09405c57', 'Review Period 1', 'CLOSED', 'd1e94b60-47c4-4945-87d1-4dc88f088e57', '926a37a4-4ded-4633-8900-715b0383aecc', '2024-09-01', '2024-09-02', '2024-09-03', '2024-01-01', '2024-12-31'); + diff --git a/web-ui/src/pages/MeritReportPage.jsx b/web-ui/src/pages/MeritReportPage.jsx index 4881dc44a..13d016381 100644 --- a/web-ui/src/pages/MeritReportPage.jsx +++ b/web-ui/src/pages/MeritReportPage.jsx @@ -18,6 +18,21 @@ import { useQueryParameters } from '../helpers/query-parameters'; import markdown from 'markdown-builder'; const MeritReportPage = () => { + const agreeMarks = [ + 'Strongly Disagree', + 'Disagree', + 'Neither Agree nor Disagree', + 'Agree', + 'Strongly Agree' + ]; + const frequencyMarks = [ + 'Very Infrequently', + 'Infrequently', + 'Neither Frequently nor Infrequently', + 'Frequently', + 'Very Frequently' + ]; + const { state, dispatch } = useContext(AppContext); const csrf = selectCsrfToken(state); @@ -222,14 +237,18 @@ const MeritReportPage = () => { }; const dateFromArray = (parts) => { - return new Date(parts[0], parts[1] - 1, parts[2]); + return (parts ? new Date(parts[0], parts[1] - 1, parts[2]) : null); }; const formatDate = (date) => { - // Date.toString() returns something like this: Wed Oct 05 2011 - // We will doctor it up to look like an American date. - let str = date.toString().slice(4, 15); - return str.slice(0, 6) + "," + str.slice(6); + if (date) { + // Date.toString() returns something like this: Wed Oct 05 2011 + // We will doctor it up to look like an American date. + let str = date.toString().slice(4, 15); + return str.slice(0, 6) + "," + str.slice(6); + } else { + return ""; + } }; const markdownTitle = (data) => { @@ -316,6 +335,26 @@ const MeritReportPage = () => { return members; }; + const getAnswerText = (answer) => { + if (answer.type == "SLIDER" || answer.type == "FREQ") { + const sentiment = parseFloat(answer.answer); + if (!isNaN(sentiment)) { + if (answer.type == "SLIDER") { + const index = sentiment * agreeMarks.length; + if (index >= 0 && index < agreeMarks.length) { + return agreeMarks[index]; + } + } else if (answer.type == "FREQ") { + const index = sentiment * frequencyMarks.length; + if (index >= 0 && index < frequencyMarks.length) { + return frequencyMarks[index]; + } + } + } + } + return answer.answer; + }; + const getUniqueQuestions = (answers) => { let questions = {}; for(let answer of answers) { @@ -324,7 +363,8 @@ const MeritReportPage = () => { // Put in member name and answer questions[key] = []; } - questions[key].push([answer.memberName, answer.answer]); + const text = getAnswerText(answer); + questions[key].push([answer.memberName, text]); } return questions; }; From df15dbeec0d8992e341de1d1fcd1da64b18ce9b0 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Thu, 5 Sep 2024 12:21:16 -0500 Subject: [PATCH 26/55] Copy from markdown automatically convert to a google doc. --- .../services/file/FileServicesImpl.java | 52 ++++++++++++++++--- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/file/FileServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/file/FileServicesImpl.java index 70991de0c..5ebe11674 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/file/FileServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/file/FileServicesImpl.java @@ -217,7 +217,6 @@ public FileInfoDTO uploadDocument(String directoryName, String name, String text boolean isAdmin = currentUserServices.isAdmin(); validate(!isAdmin, "You are not authorized to perform this operation"); - // create folder for the document try { Drive drive = googleApiAccess.getDrive(); validate(drive == null, "Unable to access Google Drive"); @@ -233,27 +232,66 @@ public FileInfoDTO uploadDocument(String directoryName, String name, String text .orElse(null); // If folder does not exist on Drive, create a new folder in the format name-date - if(folderOnDrive == null) { + if (folderOnDrive == null) { folderOnDrive = createNewDirectoryOnDrive(drive, directoryName, rootDirId); } - // set file metadata + // Set the file metadata File fileMetadata = new File(); fileMetadata.setName(name); - fileMetadata.setMimeType("application/vnd.google-apps.document"); + fileMetadata.setMimeType(MediaType.TEXT_MARKDOWN_TYPE.toString()); fileMetadata.setParents(Collections.singletonList(folderOnDrive.getId())); - //upload file to google drive + // Upload file to google drive InputStream is = new ByteArrayInputStream( StandardCharsets.UTF_8.encode(text).array()); InputStreamContent content = new InputStreamContent( - MediaType.TEXT_PLAIN_TYPE.toString(), is); + MediaType.TEXT_MARKDOWN_TYPE.toString(), is); File uploadedFile = drive.files().create(fileMetadata, content) .setSupportsAllDrives(true) .setFields("id, size, name") .execute(); - return setFileInfo(uploadedFile, null); + // See if the Google doc already exists. If it does, trash it. + FileList fileList = drive.files().list() + .setSupportsAllDrives(true) + .setIncludeItemsFromAllDrives(true) + .setQ(String.format("'%s' in parents and mimeType = 'application/vnd.google-apps.document' and trashed != true", folderOnDrive.getId())) + .setSpaces("drive") + .setFields("files(id, name, parents, size)") + .execute(); + for (File file : fileList.getFiles()) { + if (file.getName().equals(name)) { + try { + File trash = new File(); + trash.setTrashed(true); + drive.files().update(file.getId(), trash) + .setSupportsAllDrives(true) + .execute(); + } catch (GoogleJsonResponseException e) { + } catch (IOException e) { + } + } + } + + // Copy the file to a Google doc + File docFile = new File(); + docFile.setName(name); + docFile.setMimeType("application/vnd.google-apps.document"); + docFile.setParents(Collections.singletonList(folderOnDrive.getId())); + File copiedFile = drive.files().copy(uploadedFile.getId(), docFile) + .setSupportsAllDrives(true) + .setFields("id, size, name") + .execute(); + + // Delete the original mark-down file after copying it. + File trash = new File(); + trash.setTrashed(true); + drive.files().update(uploadedFile.getId(), trash) + .setSupportsAllDrives(true) + .execute(); + + return setFileInfo(copiedFile, null); } catch (GoogleJsonResponseException e) { LOG.error("Error occurred while accessing Google Drive.", e); throw new FileRetrievalException(e.getMessage()); From 9e78366d3820d20c623f7c00c0eb7db9f458aa32 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Thu, 5 Sep 2024 12:57:40 -0500 Subject: [PATCH 27/55] Added the national role entry for the current information. --- .../services/reports/CurrentInformation.java | 35 ++++--------------- web-ui/src/pages/MeritReportPage.jsx | 3 +- 2 files changed, 9 insertions(+), 29 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java index 767125d36..570135444 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java @@ -8,6 +8,9 @@ import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; +import lombok.AllArgsConstructor; +import lombok.Getter; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,40 +28,15 @@ public class CurrentInformation { + @AllArgsConstructor + @Getter public class Information { private UUID memberId; private float salary; private String range; + private String nationalRange; private String biography; private String commitments; - - public Information(UUID memberId, float salary, String range, String biography, String commitments) { - this.memberId = memberId; - this.salary = salary; - this.range = range; - this.biography = biography; - this.commitments = commitments; - } - - public UUID getMemberId() { - return memberId; - } - - public float getSalary() { - return salary; - } - - public String getRange() { - return range; - } - - public String getBiography() { - return biography; - } - - public String getCommitments() { - return commitments; - } } private static final Logger LOG = LoggerFactory.getLogger(CurrentInformation.class); @@ -90,6 +68,7 @@ public void load(MemberProfileRepository memberProfileRepository, memberProfile.get().getId(), Float.parseFloat(csvRecord.get("salary")), csvRecord.get("range"), + csvRecord.get("nationalRange"), csvRecord.get("biography"), csvRecord.get("commitments") ); diff --git a/web-ui/src/pages/MeritReportPage.jsx b/web-ui/src/pages/MeritReportPage.jsx index 13d016381..752b27b27 100644 --- a/web-ui/src/pages/MeritReportPage.jsx +++ b/web-ui/src/pages/MeritReportPage.jsx @@ -414,8 +414,9 @@ const MeritReportPage = () => { const markdownCompensation = (data) => { const currentInfo = data.currentInformation; let text = markdown.headers.h2("Compensation and Commitments"); - text += "$" + currentInfo.salary.toFixed(2) + " Base Salary\n"; + text += "$" + currentInfo.salary.toFixed(2) + " Base Salary\n\n"; text += "OCI Range for role: " + currentInfo.range + "\n\n"; + text += "National Range for role: " + currentInfo.nationalRange + "\n\n"; if (currentInfo.commitments) { text += "Commitments: " + currentInfo.commitments + "\n"; } else { From 23bfe9e2bb5692a9a3c13c31b0f885035fb198e8 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Fri, 6 Sep 2024 06:47:31 -0500 Subject: [PATCH 28/55] Fixed a minor error. --- .../com/objectcomputing/checkins/services/reports/feedback.txt | 1 - server/src/main/resources/db/dev/R__Load_testing_data.sql | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/feedback.txt b/server/src/main/java/com/objectcomputing/checkins/services/reports/feedback.txt index 690882a13..edc47caff 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/feedback.txt +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/feedback.txt @@ -11,7 +11,6 @@ FeedbackRequest (one or more) | | FeedbackAnswer | - id | - - questionId? | - requestId <------------------+ - questionId <------+ | diff --git a/server/src/main/resources/db/dev/R__Load_testing_data.sql b/server/src/main/resources/db/dev/R__Load_testing_data.sql index 8ce09005d..6f40ceec8 100644 --- a/server/src/main/resources/db/dev/R__Load_testing_data.sql +++ b/server/src/main/resources/db/dev/R__Load_testing_data.sql @@ -1538,7 +1538,7 @@ VALUES INSERT INTO feedback_answers (id, answer, question_id, request_id, sentiment) VALUES -('8c13ffa2-fad0-11eb-9a03-0242ac121134', PGP_SYM_ENCRYPT('Feeback answer.', '${aeskey}'), 'bf328e35-e486-4ec8-b3e8-acc2c09419fa', '98390c09-7121-110a-bfee-9380a470a7f1', 0); +('8c13ffa2-fad0-11eb-9a03-0242ac121134', PGP_SYM_ENCRYPT('Feedback answer.', '${aeskey}'), 'bf328e35-e486-4ec8-b3e8-acc2c09419fa', '98390c09-7121-110a-bfee-9380a470a7f1', 0); -- Skills INSERT INTO skills -- React From d8f90804ec3cf54e07730f01017a0c4b261ea1aa Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Fri, 6 Sep 2024 08:57:33 -0500 Subject: [PATCH 29/55] Create separate file selectors for the 3 types of files and no longer infer the type based on file name. --- .../reports/ReportDataController.java | 50 ++++++-- .../services/reports/ReportDataServices.java | 3 +- .../reports/ReportDataServicesImpl.java | 16 +-- web-ui/src/pages/MeritReportPage.jsx | 116 +++++++++++------- 4 files changed, 108 insertions(+), 77 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java index 8cf77e44f..d3e43a318 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java @@ -69,19 +69,43 @@ public ReportDataController(ReportDataServices reportDataServices, } @Post(uri="/upload", consumes = MediaType.MULTIPART_FORM_DATA) - public Mono> upload(@Part("file") Publisher file) { - return Flux.from(file) - .subscribeOn(Schedulers.boundedElastic()) - .map(part -> { - try { - reportDataServices.store(part); - return part.getFilename(); - } catch(IOException ex) { - LOG.error(ex.toString()); - return ""; - } - }) - .collectList(); + public Mono>> upload( + @Part("comp") Publisher comp, + @Part("curr") Publisher curr, + @Part("pos") Publisher pos) { + // There is probably an easier and better way to do this! + Mono> compHist = Flux.from(comp) + .subscribeOn(Schedulers.boundedElastic()) + .map(part -> uploadHelper( + ReportDataServices.DataType.compensationHistory, part) + ).collectList(); + Mono> currInfo = Flux.from(curr) + .subscribeOn(Schedulers.boundedElastic()) + .map(part -> uploadHelper( + ReportDataServices.DataType.currentInformation, part) + ).collectList(); + Mono> posHist = Flux.from(pos) + .subscribeOn(Schedulers.boundedElastic()) + .map(part -> uploadHelper( + ReportDataServices.DataType.positionHistory, part) + ).collectList(); + + Flux> merged = Flux.empty(); + merged = merged.mergeWith(compHist); + merged = merged.mergeWith(currInfo); + merged = merged.mergeWith(posHist); + return merged.collectList(); + } + + private String uploadHelper(ReportDataServices.DataType dataType, + CompletedFileUpload file) { + try { + reportDataServices.store(dataType, file); + return file.getFilename(); + } catch(IOException ex) { + LOG.error(ex.toString()); + return ""; + } } @Get diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServices.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServices.java index 84149c825..a8b71a0b2 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServices.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServices.java @@ -1,6 +1,5 @@ package com.objectcomputing.checkins.services.reports; -import com.objectcomputing.checkins.exceptions.BadArgException; import com.objectcomputing.checkins.exceptions.NotFoundException; import io.micronaut.http.multipart.CompletedFileUpload; @@ -13,7 +12,7 @@ public enum DataType { compensationHistory, positionHistory, currentInformation } - void store(CompletedFileUpload file) throws IOException, BadArgException; + void store(DataType dataType, CompletedFileUpload file) throws IOException; ByteBuffer get(DataType dataType) throws NotFoundException; } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java index 77c3b55e1..a1e962653 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java @@ -52,7 +52,7 @@ public ReportDataServicesImpl( @Override - public void store(CompletedFileUpload file) throws IOException, BadArgException { + public void store(DataType dataType, CompletedFileUpload file) throws IOException { MemberProfile currentUser = currentUserServices.getCurrentUser(); boolean isAdmin = currentUserServices.isAdmin(); validate(!isAdmin, NOT_AUTHORIZED_MSG); @@ -67,20 +67,6 @@ public void store(CompletedFileUpload file) throws IOException, BadArgException storedUploads.put(id, perUser); } - // Translate the file name to a data type that we know about. - String fileName = file.getFilename().toLowerCase(); - DataType dataType; - if (fileName.contains("comp")) { - dataType = DataType.compensationHistory; - } else if (fileName.contains("position")) { - dataType = DataType.positionHistory; - } else if (fileName.contains("current") || - fileName.contains("information")) { - dataType = DataType.currentInformation; - } else { - throw new BadArgException("Unable to determine data type: " + fileName); - } - // Update the timestamp to allow us to check later to see if we // need to remove this user's data. perUser.timestamp = new Date(); diff --git a/web-ui/src/pages/MeritReportPage.jsx b/web-ui/src/pages/MeritReportPage.jsx index 752b27b27..6f19de55d 100644 --- a/web-ui/src/pages/MeritReportPage.jsx +++ b/web-ui/src/pages/MeritReportPage.jsx @@ -42,7 +42,9 @@ const MeritReportPage = () => { const [searchResults, setSearchResults] = useState([]); const [allSearchResults, setAllSearchResults] = useState([]); const [editedSearchRequest, setEditedSearchRequest] = useState([]); - const [selectedFile, setSelectedFile] = useState(null); + const [selectedCompHist, setSelectedCompHist] = useState(null); + const [selectedCurrInfo, setSelectedCurrInfo] = useState(null); + const [selectedPosHist, setSelectedPosHist] = useState(null); const [reviewPeriodId, setReviewPeriodId] = useState([]); const [reviewPeriods, setReviewPeriods] = useState([]); @@ -109,51 +111,51 @@ const MeritReportPage = () => { return formatDate(date); }; - const onFileSelected = e => { - setSelectedFile(e.target.files); + const onCompHistSelected = e => { + setSelectedCompHist(e.target.files[0]); }; - const upload = async files => { - if (!files) { + const onCurrInfoSelected = e => { + setSelectedCurrInfo(e.target.files[0]); + }; + + const onPosHistSelected = e => { + setSelectedPosHist(e.target.files[0]); + }; + + const upload = async () => { + if (!selectedCompHist || !selectedCurrInfo || !selectedPosHist) { return; } - let errors; - let success = 0; - for (let i = 0; i < files.length; i++) { - const file = files[i]; - let formData = new FormData(); - formData.append('file', file); - const res = await uploadData("/services/report/data/upload", - csrf, formData); - if (res?.error) { - const error = res?.error?.message; - if (errors) { - errors += "\n" + error; - } else { - errors = error; - } - } - if (res?.payload?.data) { - success++; - } - } + const files = [ + {label: 'comp', file: selectedCompHist }, + {label: 'curr', file: selectedCurrInfo }, + {label: 'pos', file: selectedPosHist }, + ]; - if (errors) { + let formData = new FormData(); + for (let file of files) { + formData.append(file.label, file.file); + } + const res = await uploadData("/services/report/data/upload", + csrf, formData); + if (res?.error) { + const error = res?.error?.message; dispatch({ type: UPDATE_TOAST, payload: { severity: 'error', - toast: errors + toast: error } }); - } else { + } + if (res?.payload?.data) { dispatch({ type: UPDATE_TOAST, payload: { severity: 'success', - toast: success == 1 ? 'File was successfully uploaded' : - 'Files were successfully uploaded' + toast: 'Files were successfully uploaded' } }); } @@ -198,10 +200,6 @@ const MeritReportPage = () => { return data; }; - const uploadLabel = (files) => { - return files.length == 1 ? "Upload File" : "Upload Files"; - }; - const uploadDocument = async (directory, name, text) => { if (!directory || !name || !text) { return; @@ -477,30 +475,54 @@ const MeritReportPage = () => { setReviewPeriodId(newValue); }; + const checkMark = "✓"; + return (
+ +
- {selectedFile && ( - - )} +
Date: Fri, 6 Sep 2024 09:52:25 -0500 Subject: [PATCH 30/55] Added permission to create merit reports. --- .../checkins/services/permissions/Permission.java | 1 + .../checkins/services/reports/ReportDataController.java | 7 ++++++- server/src/main/resources/db/dev/R__Load_testing_data.sql | 5 +++++ .../checkins/services/fixture/PermissionFixture.java | 3 ++- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/permissions/Permission.java b/server/src/main/java/com/objectcomputing/checkins/services/permissions/Permission.java index 294207482..99db88f74 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/permissions/Permission.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/permissions/Permission.java @@ -25,6 +25,7 @@ public enum Permission { CAN_VIEW_BIRTHDAY_REPORT("View birthday report", "Reporting"), CAN_VIEW_PROFILE_REPORT("View profile report", "Reporting"), CAN_VIEW_CHECKINS_REPORT("View checkins report", "Reporting"), + CAN_CREATE_MERIT_REPORT("Create Merit Reports", "Reporting"), CAN_CREATE_CHECKINS("Create check-ins", "Check-ins"), CAN_VIEW_CHECKINS("View check-ins", "Check-ins"), CAN_UPDATE_CHECKINS("Update check-ins", "Check-ins"), diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java index d3e43a318..e9b468f6c 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java @@ -1,5 +1,7 @@ package com.objectcomputing.checkins.services.reports; +import com.objectcomputing.checkins.services.permissions.Permission; +import com.objectcomputing.checkins.services.permissions.RequiredPermission; import com.objectcomputing.checkins.services.kudos.KudosRepository; import com.objectcomputing.checkins.services.kudos.kudos_recipient.KudosRecipientRepository; import com.objectcomputing.checkins.services.memberprofile.MemberProfileRepository; @@ -69,6 +71,7 @@ public ReportDataController(ReportDataServices reportDataServices, } @Post(uri="/upload", consumes = MediaType.MULTIPART_FORM_DATA) + @RequiredPermission(Permission.CAN_CREATE_MERIT_REPORT) public Mono>> upload( @Part("comp") Publisher comp, @Part("curr") Publisher curr, @@ -109,7 +112,9 @@ private String uploadHelper(ReportDataServices.DataType dataType, } @Get - public List get(@NotNull List memberIds, @NotNull UUID reviewPeriodId) { + @RequiredPermission(Permission.CAN_CREATE_MERIT_REPORT) + public List get(@NotNull List memberIds, + @NotNull UUID reviewPeriodId) { List list = new ArrayList(); for (UUID memberId : memberIds) { ReportDataCollation data = new ReportDataCollation( diff --git a/server/src/main/resources/db/dev/R__Load_testing_data.sql b/server/src/main/resources/db/dev/R__Load_testing_data.sql index 6f40ceec8..ed11f6af0 100644 --- a/server/src/main/resources/db/dev/R__Load_testing_data.sql +++ b/server/src/main/resources/db/dev/R__Load_testing_data.sql @@ -873,6 +873,11 @@ insert into role_permissions values ('e8a4fff8-e984-4e59-be84-a713c9fa8d23', 'CAN_IMPERSONATE_MEMBERS'); +insert into role_permissions + (roleid, permission) +values + ('e8a4fff8-e984-4e59-be84-a713c9fa8d23', 'CAN_CREATE_MERIT_REPORT'); + -- PDL Permissions insert into role_permissions (roleid, permission) diff --git a/server/src/test/java/com/objectcomputing/checkins/services/fixture/PermissionFixture.java b/server/src/test/java/com/objectcomputing/checkins/services/fixture/PermissionFixture.java index e8e219f63..9b1567b10 100644 --- a/server/src/test/java/com/objectcomputing/checkins/services/fixture/PermissionFixture.java +++ b/server/src/test/java/com/objectcomputing/checkins/services/fixture/PermissionFixture.java @@ -96,7 +96,8 @@ public interface PermissionFixture extends RolePermissionFixture { Permission.CAN_ADMINISTER_DOCUMENTATION, Permission.CAN_ADMINISTER_KUDOS, Permission.CAN_CREATE_KUDOS, - Permission.CAN_IMPERSONATE_MEMBERS + Permission.CAN_IMPERSONATE_MEMBERS, + Permission.CAN_CREATE_MERIT_REPORT ); default void setPermissionsForAdmin(UUID roleID) { From 1d65888d50b9188481ad132a053dcc7588b3a8aa Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Fri, 6 Sep 2024 10:04:59 -0500 Subject: [PATCH 31/55] Clean up and simplify the code. --- .../services/file/FileServicesImpl.java | 5 ++-- .../services/reports/CompensationHistory.java | 23 ++++--------------- .../services/reports/PositionHistory.java | 23 ++++--------------- 3 files changed, 13 insertions(+), 38 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/file/FileServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/file/FileServicesImpl.java index 5ebe11674..e9c38a53c 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/file/FileServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/file/FileServicesImpl.java @@ -213,6 +213,7 @@ public FileInfoDTO uploadFile(@NotNull UUID checkInID, @NotNull CompletedFileUpl } public FileInfoDTO uploadDocument(String directoryName, String name, String text) { + final String GOOGLE_DOC_TYPE = "application/vnd.google-apps.document"; MemberProfile currentUser = currentUserServices.getCurrentUser(); boolean isAdmin = currentUserServices.isAdmin(); validate(!isAdmin, "You are not authorized to perform this operation"); @@ -256,7 +257,7 @@ public FileInfoDTO uploadDocument(String directoryName, String name, String text FileList fileList = drive.files().list() .setSupportsAllDrives(true) .setIncludeItemsFromAllDrives(true) - .setQ(String.format("'%s' in parents and mimeType = 'application/vnd.google-apps.document' and trashed != true", folderOnDrive.getId())) + .setQ(String.format("'%s' in parents and mimeType = '%s' and trashed != true", folderOnDrive.getId(), GOOGLE_DOC_TYPE)) .setSpaces("drive") .setFields("files(id, name, parents, size)") .execute(); @@ -277,7 +278,7 @@ public FileInfoDTO uploadDocument(String directoryName, String name, String text // Copy the file to a Google doc File docFile = new File(); docFile.setName(name); - docFile.setMimeType("application/vnd.google-apps.document"); + docFile.setMimeType(GOOGLE_DOC_TYPE); docFile.setParents(Collections.singletonList(folderOnDrive.getId())); File copiedFile = drive.files().copy(uploadedFile.getId(), docFile) .setSupportsAllDrives(true) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java index 92a82b417..d5d15cfc3 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java @@ -7,6 +7,9 @@ import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; +import lombok.AllArgsConstructor; +import lombok.Getter; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,28 +27,12 @@ public class CompensationHistory { + @AllArgsConstructor + @Getter public class Compensation { private UUID memberId; private LocalDate startDate; private float amount; - - public Compensation(UUID memberId, LocalDate startDate, float amount) { - this.memberId = memberId; - this.startDate = startDate; - this.amount = amount; - } - - public UUID getMemberId() { - return memberId; - } - - public LocalDate getStartDate() { - return startDate; - } - - public float getAmount() { - return amount; - } } private static final Logger LOG = LoggerFactory.getLogger(CompensationHistory.class); diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/PositionHistory.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/PositionHistory.java index bd6736f1f..1776aa92f 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/PositionHistory.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/PositionHistory.java @@ -7,6 +7,9 @@ import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; +import lombok.AllArgsConstructor; +import lombok.Getter; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,28 +30,12 @@ public class PositionHistory { + @AllArgsConstructor + @Getter public class Position { private UUID memberId; private LocalDate date; private String title; - - public Position(UUID memberId, LocalDate date, String title) { - this.memberId = memberId; - this.date = date; - this.title = title; - } - - public UUID getMemberId() { - return memberId; - } - - public LocalDate getDate() { - return date; - } - - public String getTitle() { - return title; - } } private static final Logger LOG = LoggerFactory.getLogger(PositionHistory.class); From d6fc0e1ad1a5ae035e72ecf1402e9613c527849c Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Fri, 6 Sep 2024 13:39:45 -0500 Subject: [PATCH 32/55] Modified how Kudos are returned so that we can include the sender name. --- .../services/reports/ReportDataCollation.java | 12 ++++++++--- .../services/reports/ReportDataDTO.java | 3 +-- .../services/reports/ReportKudos.java | 15 ++++++++++++++ .../resources/db/dev/R__Load_testing_data.sql | 20 +++++++++++++++++++ web-ui/src/pages/MeritReportPage.jsx | 8 +++++--- 5 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 server/src/main/java/com/objectcomputing/checkins/services/reports/ReportKudos.java diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java index 3d040c49f..ac2e25635 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java @@ -107,9 +107,9 @@ LocalDate getEndDate() { } /// Get the kudos given to the member during the start and end date range. - public List getKudos() { + public List getKudos() { List recipients = kudosRecipientRepository.findByMemberId(memberId); - List kudosList = new ArrayList(); + List kudosList = new ArrayList(); for (KudosRecipient recipient : recipients) { Kudos kudos = kudosRepository.findById(recipient.getKudosId()) .orElse(null); @@ -117,7 +117,13 @@ public List getKudos() { LocalDate created = kudos.getDateCreated(); if ((created.isEqual(startDate) || created.isAfter(startDate)) && created.isBefore(endDate)) { - kudosList.add(kudos); + MemberProfile senderProfile = + memberProfileRepository.findById(kudos.getSenderId()).orElse(null); + String sender = senderProfile == null ? + "Unknown" : + MemberProfileUtils.getFullName(senderProfile); + kudosList.add(new ReportKudos(created, + kudos.getMessage(), sender)); } } } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java index 35c118536..55c804254 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java @@ -1,7 +1,6 @@ package com.objectcomputing.checkins.services.reports; import com.objectcomputing.checkins.services.memberprofile.MemberProfile; -import com.objectcomputing.checkins.services.kudos.Kudos; import io.micronaut.core.annotation.Introspected; import io.micronaut.core.annotation.Nullable; import jakarta.validation.constraints.NotBlank; @@ -37,7 +36,7 @@ public class ReportDataDTO { private MemberProfile memberProfile; @NotNull - private List kudos; + private List kudos; @NotNull private List compensationHistory; diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportKudos.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportKudos.java new file mode 100644 index 000000000..df49bee44 --- /dev/null +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportKudos.java @@ -0,0 +1,15 @@ +package com.objectcomputing.checkins.services.reports; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.time.LocalDate; + +@AllArgsConstructor +@Getter +class ReportKudos { + private final LocalDate dateCreated; + private final String message; + private final String sender; +} + diff --git a/server/src/main/resources/db/dev/R__Load_testing_data.sql b/server/src/main/resources/db/dev/R__Load_testing_data.sql index ed11f6af0..5c51d8c4d 100644 --- a/server/src/main/resources/db/dev/R__Load_testing_data.sql +++ b/server/src/main/resources/db/dev/R__Load_testing_data.sql @@ -1687,6 +1687,26 @@ INSERT INTO kudos_recipient VALUES ('f792056b-22ce-4e3d-a442-0fdc3cb35e7b', '9cdce399-4c02-41ed-a63f-35beb6ecb622', '105f2968-a182-45a3-892c-eeff76383fe0'); +INSERT INTO kudos +(id, message, senderid, teamid, datecreated, dateapproved) +VALUES +('39dfd284-d0bf-4017-848c-8156dfef2b93', PGP_SYM_ENCRYPT('Kudos are tasty.', '${aeskey}'), '6207b3fd-042d-49aa-9e28-dcc04f537c2d', null, '2024-09-04', '2022-09-04'); + +INSERT INTO kudos_recipient +(id, kudosid, memberid) +VALUES +('ebc023e2-b578-4b03-a2fc-fc9472a8474b', '39dfd284-d0bf-4017-848c-8156dfef2b93', 'c7406157-a38f-4d48-aaed-04018d846727'); + +INSERT INTO kudos +(id, message, senderid, teamid, datecreated, dateapproved) +VALUES +('39dfd284-d0bf-4017-848c-8156dfef2b94', PGP_SYM_ENCRYPT('Kudos are covered in chocolate.', '${aeskey}'), '6207b3fd-042d-49aa-9e28-dcc04f537c2d', null, '2024-09-04', '2022-09-04'); + +INSERT INTO kudos_recipient +(id, kudosid, memberid) +VALUES +('ebc023e2-b578-4b03-a2fc-fc9472a8474c', '39dfd284-d0bf-4017-848c-8156dfef2b94', 'c7406157-a38f-4d48-aaed-04018d846727'); + -- Skill Categories INSERT INTO skillcategories (id, name, description) diff --git a/web-ui/src/pages/MeritReportPage.jsx b/web-ui/src/pages/MeritReportPage.jsx index 6f19de55d..f737dfe6f 100644 --- a/web-ui/src/pages/MeritReportPage.jsx +++ b/web-ui/src/pages/MeritReportPage.jsx @@ -278,9 +278,11 @@ const MeritReportPage = () => { let text = markdown.headers.h1("Kudos"); for (let kudos of kudosList) { const date = dateFromArray(kudos.dateCreated); - text += kudos.message + "\n"; - text += markdown.emphasis.i("     " + - "Submitted on " + formatDate(date)) + "\n\n"; + text += kudos.message + "\n\n"; + text += "     " + + markdown.emphasis.i("Submitted on " + formatDate(date) + + ", by " + kudos.sender) + + "\n\n\n"; } return text; }; From bc397972ab6f6fd9cdd68cb64d559791e0f76563 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Mon, 9 Sep 2024 08:54:41 -0500 Subject: [PATCH 33/55] Added tests for the report data controller. --- .../reports/ReportDataControllerTest.java | 132 ++++++++++++++++++ .../reports/data/compensationHistory.csv | 11 ++ .../reports/data/currentInformation.csv | 2 + .../services/reports/data/positionHistory.csv | 8 ++ 4 files changed, 153 insertions(+) create mode 100644 server/src/test/java/com/objectcomputing/checkins/services/reports/ReportDataControllerTest.java create mode 100644 server/src/test/java/com/objectcomputing/checkins/services/reports/data/compensationHistory.csv create mode 100644 server/src/test/java/com/objectcomputing/checkins/services/reports/data/currentInformation.csv create mode 100644 server/src/test/java/com/objectcomputing/checkins/services/reports/data/positionHistory.csv diff --git a/server/src/test/java/com/objectcomputing/checkins/services/reports/ReportDataControllerTest.java b/server/src/test/java/com/objectcomputing/checkins/services/reports/ReportDataControllerTest.java new file mode 100644 index 000000000..ec14c9314 --- /dev/null +++ b/server/src/test/java/com/objectcomputing/checkins/services/reports/ReportDataControllerTest.java @@ -0,0 +1,132 @@ +package com.objectcomputing.checkins.services.reports; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.objectcomputing.checkins.services.TestContainersSuite; +import com.objectcomputing.checkins.services.fixture.RoleFixture; +import com.objectcomputing.checkins.services.fixture.MemberProfileFixture; +import com.objectcomputing.checkins.services.memberprofile.MemberProfile; + +import io.micronaut.core.type.Argument; +import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.MutableHttpRequest; +import io.micronaut.http.client.HttpClient; +import io.micronaut.http.client.annotation.Client; +import io.micronaut.http.client.exceptions.HttpClientResponseException; +import io.micronaut.http.client.multipart.MultipartBody; + +import jakarta.inject.Inject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import static com.objectcomputing.checkins.services.role.RoleType.Constants.ADMIN_ROLE; +import static com.objectcomputing.checkins.services.role.RoleType.Constants.MEMBER_ROLE; +import static io.micronaut.http.MediaType.MULTIPART_FORM_DATA; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class ReportDataControllerTest extends TestContainersSuite implements MemberProfileFixture, RoleFixture { + + @Inject + @Client("/services/report/data") + HttpClient client; + + private MemberProfile regular; + private MemberProfile admin; + private final String basePath = "src/test/java/com/objectcomputing/checkins/services/reports/"; + + @BeforeEach + void createRolesAndPermissions() { + createAndAssignRoles(); + regular = createADefaultMemberProfile(); + admin = createAThirdDefaultMemberProfile(); + assignAdminRole(admin); + } + + @Test + void uploadReportData() { + final HttpRequest request = postData(admin, ADMIN_ROLE); + final String response = client.toBlocking().retrieve(request); + assertNotNull(response); + } + + @Test + void uploadReportDataWithoutPermission() { + final HttpRequest request = postData(regular, MEMBER_ROLE); + HttpClientResponseException responseException = + assertThrows(HttpClientResponseException.class, + () -> client.toBlocking().retrieve(request)); + } + + @Test + void getReportData() throws JsonProcessingException { + HttpRequest request = postData(admin, ADMIN_ROLE); + final String response = client.toBlocking().retrieve(request); + assertNotNull(response); + + request = HttpRequest.GET( + String.format("/?memberIds=%s&reviewPeriodId=%s", + regular.getId(), + "12345678-e29c-4cf4-9ea4-6baa09405c57")) + .basicAuth(admin.getWorkEmail(), ADMIN_ROLE); + final String data = client.toBlocking().retrieve(request); + ObjectMapper objectMapper = new ObjectMapper(); + assertNotNull(data); + + // Perform minimal validation of returned data + JsonNode root = objectMapper.readTree(data); + assertEquals(root.isArray(), true); + assertEquals(root.isEmpty(), false); + + ArrayNode arrayNode = (ArrayNode)root; + JsonNode first = arrayNode.get(0); + assertNotNull(first.get("memberProfile")); + assertNotNull(first.get("kudos")); + assertNotNull(first.get("compensationHistory")); + assertNotNull(first.get("currentInformation")); + assertNotNull(first.get("positionHistory")); + assertNotNull(first.get("selfReviews")); + assertNotNull(first.get("reviews")); + assertNotNull(first.get("feedback")); + } + + @Test + void getReportDataWithoutPermission() { + final HttpRequest request = HttpRequest.GET( + String.format("/?memberIds=%s&reviewPeriodId=%s", + regular.getId(), + "12345678-e29c-4cf4-9ea4-6baa09405c57")) + .basicAuth(regular.getWorkEmail(), MEMBER_ROLE); + HttpClientResponseException responseException = + assertThrows(HttpClientResponseException.class, + () -> client.toBlocking().retrieve(request)); + } + + HttpRequest postData(MemberProfile user, String role) { + File compFile = new File(basePath + "data/compensationHistory.csv"); + File currFile = new File(basePath + "data/currentInformation.csv"); + File posFile = new File(basePath + "data/positionHistory.csv"); + + return HttpRequest.POST("/upload", + MultipartBody.builder() + .addPart("comp", compFile) + .addPart("curr", currFile) + .addPart("pos", posFile) + .build()) + .basicAuth(user.getWorkEmail(), role) + .contentType(MULTIPART_FORM_DATA); + } +} diff --git a/server/src/test/java/com/objectcomputing/checkins/services/reports/data/compensationHistory.csv b/server/src/test/java/com/objectcomputing/checkins/services/reports/data/compensationHistory.csv new file mode 100644 index 000000000..2306d7ea3 --- /dev/null +++ b/server/src/test/java/com/objectcomputing/checkins/services/reports/data/compensationHistory.csv @@ -0,0 +1,11 @@ +"emailAddress","startDate","compensation" +"billm@objectcomputing.com","1/1/2024","100000.00" +"billm@objectcomputing.com","1/1/2023","95000.00" +"billm@objectcomputing.com","1/1/2022","90000.00" +"billm@objectcomputing.com","1/1/2021","85000.00" +"billm@objectcomputing.com","1/1/2020","80000.00" +"otheremail@objectcomputing.com","2/1/2024","100000.00" +"otheremail@objectcomputing.com","2/1/2023","95000.00" +"otheremail@objectcomputing.com","2/1/2022","90000.00" +"otheremail@objectcomputing.com","2/1/2021","85000.00" +"otheremail@objectcomputing.com","2/1/2020","80000.00" diff --git a/server/src/test/java/com/objectcomputing/checkins/services/reports/data/currentInformation.csv b/server/src/test/java/com/objectcomputing/checkins/services/reports/data/currentInformation.csv new file mode 100644 index 000000000..32e882221 --- /dev/null +++ b/server/src/test/java/com/objectcomputing/checkins/services/reports/data/currentInformation.csv @@ -0,0 +1,2 @@ +"emailAddress","salary","range","nationalRange", "biography","commitments" +"billm@objectcomputing.com",122222,"$90000 – $150000","$89000 - $155000","Lives in St. Louis","" diff --git a/server/src/test/java/com/objectcomputing/checkins/services/reports/data/positionHistory.csv b/server/src/test/java/com/objectcomputing/checkins/services/reports/data/positionHistory.csv new file mode 100644 index 000000000..084655072 --- /dev/null +++ b/server/src/test/java/com/objectcomputing/checkins/services/reports/data/positionHistory.csv @@ -0,0 +1,8 @@ +"emailAddress","date","title" +"otheremail@objectcomputing.com",5/11/2018,Director of People (previously Org. Dev.) +"otheremail@objectcomputing.com",2014,Principal Software Engineer +"otheremail@objectcomputing.com",2009,Senior Software Engineer +"otheremail@objectcomputing.com",2000,Software Engineer +"billm@objectcomputing.com",2007,Principal Software Engineer +"billm@objectcomputing.com",2000,Senior Software Engineer +"billm@objectcomputing.com",8/1/1996,Software Engineer From 9d724f9ce3a772d820732e43f6045911aeaa7b35 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Mon, 9 Sep 2024 09:25:54 -0500 Subject: [PATCH 34/55] Deal with dates in different formats and non-numeric symbols in salary fields. --- .../services/reports/CompensationHistory.java | 35 ++++++++++++++++--- .../services/reports/CurrentInformation.java | 3 +- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java index d5d15cfc3..118405a12 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java @@ -19,6 +19,9 @@ import java.io.IOException; import java.time.LocalDate; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -60,11 +63,17 @@ public void load(MemberProfileRepository memberProfileRepository, Optional memberProfile = memberProfileRepository.findByWorkEmail(emailAddress); if (memberProfile.isPresent()) { - Compensation comp = new Compensation( - memberProfile.get().getId(), - LocalDate.parse(csvRecord.get("startDate"), formatter), - Float.parseFloat(csvRecord.get("compensation"))); - history.add(comp); + LocalDate date = parseDate(csvRecord.get("startDate")); + if (date == null) { + LOG.error("Unable to parse date: " + csvRecord.get("startDate")); + } else { + Compensation comp = new Compensation( + memberProfile.get().getId(), + date, + Float.parseFloat(csvRecord.get("compensation") + .replaceAll("[^\\d\\.,]", ""))); + history.add(comp); + } } else { LOG.error("Unable to find a profile for " + emailAddress); } @@ -76,4 +85,20 @@ public List getHistory(UUID memberId) { .filter(entry -> entry.getMemberId().equals(memberId)) .collect(Collectors.toList()); } + + private LocalDate parseDate(String date) { + List formatStrings = List.of("yyyy", "M/d/yyyy"); + for(String format: formatStrings) { + try { + return LocalDate.parse(date, + new DateTimeFormatterBuilder() + .appendPattern(format) + .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1) + .parseDefaulting(ChronoField.DAY_OF_MONTH, 1) + .toFormatter()); + } catch(DateTimeParseException ex) { + } + } + return null; + } } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java index 570135444..1fe18626a 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java @@ -66,7 +66,8 @@ public void load(MemberProfileRepository memberProfileRepository, if (memberProfile.isPresent()) { Information comp = new Information( memberProfile.get().getId(), - Float.parseFloat(csvRecord.get("salary")), + Float.parseFloat(csvRecord.get("salary") + .replaceAll("[^\\d\\.,]", "")), csvRecord.get("range"), csvRecord.get("nationalRange"), csvRecord.get("biography"), From b1850c9a54f21c3979f068918ce83d48515ffc6a Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Mon, 9 Sep 2024 10:27:22 -0500 Subject: [PATCH 35/55] Sort the questions by question number and added more newline characters to ensure that normal text is separated when the markdown is converted to a google doc. --- .../checkins/services/reports/Feedback.java | 1 + .../services/reports/ReportDataCollation.java | 5 ++- .../resources/db/dev/R__Load_testing_data.sql | 45 +++++++++++++++++++ web-ui/src/pages/MeritReportPage.jsx | 14 +++--- 4 files changed, 59 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/Feedback.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/Feedback.java index 0dea90e3e..35673d4c3 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/Feedback.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/Feedback.java @@ -19,6 +19,7 @@ public static class Answer { private final String question; private final String answer; private final String type; + private final int number; } private String name; diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java index ac2e25635..6f0967ab3 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java @@ -244,11 +244,13 @@ private List getFeedbackType(FeedbackType type) { for (FeedbackAnswer answer : answers) { String questionText; String questionType = textQuestion; + int questionNumber = 0; try { TemplateQuestion question = templateQuestionServices.getById(answer.getQuestionId()); questionText = question.getQuestion(); questionType = question.getInputType(); + questionNumber = question.getQuestionNumber(); } catch(NotFoundException ex) { LOG.error(ex.toString()); questionText = answer.getQuestionId().toString(); @@ -261,7 +263,8 @@ private List getFeedbackType(FeedbackType type) { questionType.equals(radioQuestion) ? answer.getAnswer() : String.valueOf(answer.getSentiment()), - questionType)); + questionType, + questionNumber)); } } } diff --git a/server/src/main/resources/db/dev/R__Load_testing_data.sql b/server/src/main/resources/db/dev/R__Load_testing_data.sql index 5c51d8c4d..e90c3f199 100644 --- a/server/src/main/resources/db/dev/R__Load_testing_data.sql +++ b/server/src/main/resources/db/dev/R__Load_testing_data.sql @@ -1284,12 +1284,22 @@ INSERT INTO feedback_requests VALUES ('98390c09-7121-110a-bfee-9380a470a7f0', '72655c4f-1fb8-4514-b31e-7f7e19fa9bd7', 'c7406157-a38f-4d48-aaed-04018d846727', '6207b3fd-042d-49aa-9e28-dcc04f537c2d', 'd1e94b60-47c4-4945-87d1-4dc88f088e57', '2024-09-04', '2024-09-30', '2024-09-05', 'sent'); +INSERT INTO feedback_requests +(id, creator_id, requestee_id, recipient_id, template_id, send_date, due_date, submit_date, status) +VALUES +('98390c09-7121-110a-bfee-9380a470a7f3', '72655c4f-1fb8-4514-b31e-7f7e19fa9bd7', 'c7406157-a38f-4d48-aaed-04018d846727', 'dfe2f986-fac0-11eb-9a03-0242ac130003', 'd1e94b60-47c4-4945-87d1-4dc88f088e57', '2024-09-04', '2024-09-30', '2024-09-05', 'sent'); + -- CAE - Feedback request, Creator: Big Boss INSERT INTO feedback_requests (id, creator_id, requestee_id, recipient_id, template_id, send_date, due_date, submit_date, status) VALUES ('98390c09-7121-110a-bfee-9380a470a7f1', '72655c4f-1fb8-4514-b31e-7f7e19fa9bd7', 'c7406157-a38f-4d48-aaed-04018d846727', '6207b3fd-042d-49aa-9e28-dcc04f537c2d', '1c8bc142-c447-4889-986e-42ab177da683', '2024-09-04', '2024-09-30', '2024-09-05', 'sent'); +INSERT INTO feedback_requests +(id, creator_id, requestee_id, recipient_id, template_id, send_date, due_date, submit_date, status) +VALUES +('98390c09-7121-110a-bfee-9380a470a7f2', '72655c4f-1fb8-4514-b31e-7f7e19fa9bd7', 'c7406157-a38f-4d48-aaed-04018d846727', 'dfe2f986-fac0-11eb-9a03-0242ac130003', '1c8bc142-c447-4889-986e-42ab177da683', '2024-09-04', '2024-09-30', '2024-09-05', 'sent'); + ---- Creator: Big Boss INSERT INTO feedback_requests (id, creator_id, requestee_id, recipient_id, template_id, send_date, due_date, submit_date, status) -- requestee: Faux Freddy, recipient: Unreal Ulysses @@ -1528,6 +1538,26 @@ INSERT INTO feedback_answers VALUES ('8c13ffa2-fad0-11eb-9a03-0242ac121131', PGP_SYM_ENCRYPT('Text for the last question.', '${aeskey}'), '174e5851-cb24-4a0f-890c-e6f041db4127', '98390c09-7121-110a-bfee-9380a470a7f0', 0); +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121138', PGP_SYM_ENCRYPT('', '${aeskey}'), '6eff224d-d690-4d25-a44f-08b8ec03fbbe', '98390c09-7121-110a-bfee-9380a470a7f3', 0.6); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121139', PGP_SYM_ENCRYPT('', '${aeskey}'), '47758dfb-64ca-4203-a5ba-b5fa3ef254dd', '98390c09-7121-110a-bfee-9380a470a7f3', .4); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac12113a', PGP_SYM_ENCRYPT('', '${aeskey}'), '4ab44c4f-bd8f-4e8d-89b7-940ada1a45c0', '98390c09-7121-110a-bfee-9380a470a7f3', .4); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac12113b', PGP_SYM_ENCRYPT('Other text for this question.', '${aeskey}'), '93424f36-64a3-4f10-b78a-00de58060177', '98390c09-7121-110a-bfee-9380a470a7f3', 0); + -- CAE - Feedback 1c8bc142-c447-4889-986e-42ab177da683 answers INSERT INTO feedback_answers @@ -1545,6 +1575,21 @@ INSERT INTO feedback_answers VALUES ('8c13ffa2-fad0-11eb-9a03-0242ac121134', PGP_SYM_ENCRYPT('Feedback answer.', '${aeskey}'), 'bf328e35-e486-4ec8-b3e8-acc2c09419fa', '98390c09-7121-110a-bfee-9380a470a7f1', 0); +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121135', PGP_SYM_ENCRYPT('Yes', '${aeskey}'), '22113310-04dd-4931-96f2-37303a2515a4', '98390c09-7121-110a-bfee-9380a470a7f2', 0); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121136', PGP_SYM_ENCRYPT('', '${aeskey}'), '11d7b14c-2eee-4f72-a2b6-8c57a094207e', '98390c09-7121-110a-bfee-9380a470a7f2', 0.6); + +INSERT INTO feedback_answers +(id, answer, question_id, request_id, sentiment) +VALUES +('8c13ffa2-fad0-11eb-9a03-0242ac121137', PGP_SYM_ENCRYPT('Different feedback answer.', '${aeskey}'), 'bf328e35-e486-4ec8-b3e8-acc2c09419fa', '98390c09-7121-110a-bfee-9380a470a7f2', 0); + -- Skills INSERT INTO skills -- React (id, name, pending, description, extraneous) diff --git a/web-ui/src/pages/MeritReportPage.jsx b/web-ui/src/pages/MeritReportPage.jsx index f737dfe6f..a7344f584 100644 --- a/web-ui/src/pages/MeritReportPage.jsx +++ b/web-ui/src/pages/MeritReportPage.jsx @@ -255,7 +255,7 @@ const MeritReportPage = () => { const endDate = dateFromArray(data.endDate); let text = markdown.headers.h1(memberProfile.firstName + " " + memberProfile.lastName); - text += memberProfile.title + "\n"; + text += memberProfile.title + "\n\n"; text += "Review Period: " + formatDate(startDate) + " - " + formatDate(endDate) + "\n\n"; return text; @@ -295,7 +295,7 @@ const MeritReportPage = () => { if (listMembers) { text += member + ": "; } - text += "Submitted - " + formatDate(members[member]) + "\n"; + text += "Submitted - " + formatDate(members[member]) + "\n\n"; } text += "\n"; @@ -306,7 +306,7 @@ const MeritReportPage = () => { if (listMembers) { text += answer[0] + ": "; } - text += answer[1] + "\n"; + text += answer[1] + "\n\n"; } text += "\n"; } @@ -357,6 +357,10 @@ const MeritReportPage = () => { const getUniqueQuestions = (answers) => { let questions = {}; + answers = answers.sort((a, b) => { + return a.number - b.number; + }); + for(let answer of answers) { const key = answer.question; if (!(key in questions)) { @@ -376,7 +380,7 @@ const MeritReportPage = () => { text += markdown.headers.h2("Template: " + feedback.name); const members = getUniqueMembers(feedback.answers); for(let member of Object.keys(members)) { - text += member + ": " + formatDate(members[member]) + "\n"; + text += member + ": " + formatDate(members[member]) + "\n\n"; } text += "\n"; @@ -384,7 +388,7 @@ const MeritReportPage = () => { for(let question of Object.keys(questions)) { text += markdown.headers.h3(question) + "\n"; for(let answer of questions[question]) { - text += answer[0] + ": " + answer[1] + "\n"; + text += answer[0] + ": " + answer[1] + "\n\n"; } text += "\n"; } From bee7d06bdbc566ba6d83f6ff739ffe20fc50cc3a Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Mon, 9 Sep 2024 12:52:11 -0500 Subject: [PATCH 36/55] Initial addition of employee hours. --- .../services/reports/ReportDataCollation.java | 33 ++++++++++++++++++- .../reports/ReportDataController.java | 12 +++++-- .../services/reports/ReportDataDTO.java | 3 ++ web-ui/src/pages/MeritReportPage.jsx | 14 ++++++++ 4 files changed, 58 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java index 6f0967ab3..7c5bdcbd6 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java @@ -19,6 +19,8 @@ import com.objectcomputing.checkins.services.feedback_answer.FeedbackAnswer; import com.objectcomputing.checkins.services.feedback_template.template_question.TemplateQuestionServices; import com.objectcomputing.checkins.services.feedback_template.template_question.TemplateQuestion; +import com.objectcomputing.checkins.services.employee_hours.EmployeeHoursServices; +import com.objectcomputing.checkins.services.employee_hours.EmployeeHours; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,6 +29,7 @@ import java.util.List; import java.util.ArrayList; import java.util.Map; +import java.util.Set; import java.util.HashMap; import java.time.LocalDate; import java.time.Month; @@ -67,6 +70,7 @@ private enum FeedbackType { private FeedbackRequestServices feedbackRequestServices; private FeedbackAnswerServices feedbackAnswerServices; private TemplateQuestionServices templateQuestionServices; + private EmployeeHoursServices employeeHoursServices; public ReportDataCollation( UUID memberId, UUID reviewPeriodId, @@ -78,7 +82,8 @@ public ReportDataCollation( FeedbackTemplateServices feedbackTemplateServices, FeedbackRequestServices feedbackRequestServices, FeedbackAnswerServices feedbackAnswerServices, - TemplateQuestionServices templateQuestionServices) { + TemplateQuestionServices templateQuestionServices, + EmployeeHoursServices employeeHoursServices) { this.memberId = memberId; this.reviewPeriodId = reviewPeriodId; this.compensationHistory = new CompensationHistory(); @@ -93,6 +98,7 @@ public ReportDataCollation( this.feedbackRequestServices = feedbackRequestServices; this.feedbackAnswerServices = feedbackAnswerServices; this.templateQuestionServices = templateQuestionServices; + this.employeeHoursServices = employeeHoursServices; LocalDateRange range = getDateRange(); startDate = range.start; endDate = range.end; @@ -179,6 +185,31 @@ public List getFeedback() { return getFeedbackType(FeedbackType.feedback); } + public ReportHours getReportHours() { + MemberProfile memberProfile = getMemberProfile(); + Set employeeHours = + employeeHoursServices.findByFields(memberProfile.getEmployeeId()); + float contributionHours = 0; + float ptoHours = 0; + float overtimeHours = 0; + float billableUtilization = 0; + for (EmployeeHours hours: employeeHours) { + contributionHours += hours.getContributionHours(); + ptoHours += hours.getPtoHours(); + // Waiting for Feature 2584 to be merged. + //Float otw = hours.getOvertimeWorked(); + //if (otw != null) { + // overtimeHours += otw; + //} + //Float bu = hours.getBillableUtilization(); + //if (bu != null) { + // billableUtilization += bu; + //} + } + return new ReportHours(contributionHours, ptoHours, + overtimeHours, billableUtilization); + } + private List getFeedbackType(FeedbackType type) { List feedback = new ArrayList(); diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java index e9b468f6c..1d192f6d8 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java @@ -10,6 +10,7 @@ import com.objectcomputing.checkins.services.feedback_request.FeedbackRequestServices; import com.objectcomputing.checkins.services.feedback_answer.FeedbackAnswerServices; import com.objectcomputing.checkins.services.feedback_template.template_question.TemplateQuestionServices; +import com.objectcomputing.checkins.services.employee_hours.EmployeeHoursServices; import com.objectcomputing.checkins.exceptions.NotFoundException; import io.micronaut.http.MediaType; import io.micronaut.http.annotation.Controller; @@ -49,6 +50,7 @@ public class ReportDataController { private final FeedbackRequestServices feedbackRequestServices; private final FeedbackAnswerServices feedbackAnswerServices; private final TemplateQuestionServices templateQuestionServices; + private final EmployeeHoursServices employeeHoursServices; public ReportDataController(ReportDataServices reportDataServices, KudosRepository kudosRepository, @@ -58,7 +60,8 @@ public ReportDataController(ReportDataServices reportDataServices, FeedbackTemplateServices feedbackTemplateServices, FeedbackRequestServices feedbackRequestServices, FeedbackAnswerServices feedbackAnswerServices, - TemplateQuestionServices templateQuestionServices) { + TemplateQuestionServices templateQuestionServices, + EmployeeHoursServices employeeHoursServices) { this.reportDataServices = reportDataServices; this.kudosRepository = kudosRepository; this.kudosRecipientRepository = kudosRecipientRepository; @@ -68,6 +71,7 @@ public ReportDataController(ReportDataServices reportDataServices, this.feedbackRequestServices = feedbackRequestServices; this.feedbackAnswerServices = feedbackAnswerServices; this.templateQuestionServices = templateQuestionServices; + this.employeeHoursServices = employeeHoursServices; } @Post(uri="/upload", consumes = MediaType.MULTIPART_FORM_DATA) @@ -127,7 +131,8 @@ public List get(@NotNull List memberIds, feedbackTemplateServices, feedbackRequestServices, feedbackAnswerServices, - templateQuestionServices); + templateQuestionServices, + employeeHoursServices); list.add(new ReportDataDTO(memberId, reviewPeriodId, data.getStartDate(), data.getEndDate(), data.getMemberProfile(), data.getKudos(), @@ -136,7 +141,8 @@ public List get(@NotNull List memberIds, data.getPositionHistory(), data.getSelfReviews(), data.getReviews(), - data.getFeedback())); + data.getFeedback(), + data.getReportHours())); } return list; } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java index 55c804254..e495e167b 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java @@ -55,4 +55,7 @@ public class ReportDataDTO { @NotNull private List feedback; + + @NotNull + private ReportHours hours; } diff --git a/web-ui/src/pages/MeritReportPage.jsx b/web-ui/src/pages/MeritReportPage.jsx index a7344f584..0d3389a8b 100644 --- a/web-ui/src/pages/MeritReportPage.jsx +++ b/web-ui/src/pages/MeritReportPage.jsx @@ -448,6 +448,19 @@ const MeritReportPage = () => { return text; }; + const markdownEmployeeHours = (data) => { + let text = markdown.headers.h2("Employee Hours"); + let hours = { + 'Contribution Hours': data.hours.contributionHours, + 'PTO Hours': data.hours.ptoHours, + 'Overtime Hours': data.hours.overtimeHours, + 'Billable Utilization': data.hours.billableUtilization, + }; + text += markdown.lists.ul(Object.keys(hours), + (key) => key + ": " + hours[key]); + return text; + }; + const markdownReviewerNotes = (data) => { let text = markdown.headers.h4("Reviewer Notes"); return text; @@ -465,6 +478,7 @@ const MeritReportPage = () => { text += markdownReviews(data); text += markdownFeedback(data); text += markdownTitleHistory(data); + text += markdownEmployeeHours(data); text += markdownCompensation(data); text += markdownCompensationHistory(data); text += markdownReviewerNotes(data); From 372911979ebaecc7a73a85bae26ae241251abbad Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Mon, 9 Sep 2024 12:52:36 -0500 Subject: [PATCH 37/55] Initial addition of employee hours. --- .../checkins/services/reports/ReportHours.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 server/src/main/java/com/objectcomputing/checkins/services/reports/ReportHours.java diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportHours.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportHours.java new file mode 100644 index 000000000..119aa9f77 --- /dev/null +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportHours.java @@ -0,0 +1,15 @@ +package com.objectcomputing.checkins.services.reports; +import io.micronaut.core.annotation.Introspected; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +@Introspected +class ReportHours { + private final float contributionHours; + private final float ptoHours; + private final float overtimeHours; + private final float billableUtilization; +} + From fd002c9c4eb0b09e5ba2859c035a2dc56b3130aa Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Mon, 9 Sep 2024 13:09:58 -0500 Subject: [PATCH 38/55] Added a warning log message for data expiration. --- .../checkins/services/reports/ReportDataServicesImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java index a1e962653..c40611064 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java @@ -104,6 +104,7 @@ public void run() { for (Map.Entry entry : storedUploads.entrySet()) { Stored value = entry.getValue(); if (current >= (value.timestamp.getTime() + expiration)) { + LOG.warn("Removing stored data for " + entry.getKey().toString()); storedUploads.remove(entry.getKey()); } } From b941dc97955c469dcc80ecbd7ea783ec3c4bd326 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Mon, 9 Sep 2024 14:26:22 -0500 Subject: [PATCH 39/55] Added in fields from Feature 2583. --- .../services/reports/ReportDataCollation.java | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java index 7c5bdcbd6..be043c6ab 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java @@ -194,17 +194,21 @@ public ReportHours getReportHours() { float overtimeHours = 0; float billableUtilization = 0; for (EmployeeHours hours: employeeHours) { - contributionHours += hours.getContributionHours(); - ptoHours += hours.getPtoHours(); - // Waiting for Feature 2584 to be merged. - //Float otw = hours.getOvertimeWorked(); - //if (otw != null) { - // overtimeHours += otw; - //} - //Float bu = hours.getBillableUtilization(); - //if (bu != null) { - // billableUtilization += bu; - //} + LocalDate date = hours.getAsOfDate(); + if ((date.isEqual(startDate) || + date.isAfter(startDate)) && date.isBefore(endDate)) { + contributionHours += hours.getContributionHours(); + ptoHours += hours.getPtoHours(); + + Float otw = hours.getOvertimeWorked(); + if (otw != null) { + overtimeHours += otw; + } + Float bu = hours.getBillableUtilization(); + if (bu != null) { + billableUtilization += bu; + } + } } return new ReportHours(contributionHours, ptoHours, overtimeHours, billableUtilization); From b6592b1224b9bf542941e22b44801316c60c958d Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Mon, 9 Sep 2024 14:27:29 -0500 Subject: [PATCH 40/55] Gracefully handle csv parse errors. --- .../checkins/services/reports/CompensationHistory.java | 8 ++++++-- .../checkins/services/reports/CurrentInformation.java | 7 ++++++- .../checkins/services/reports/PositionHistory.java | 7 ++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java index 118405a12..614b54f09 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java @@ -2,6 +2,7 @@ import com.objectcomputing.checkins.services.memberprofile.MemberProfile; import com.objectcomputing.checkins.services.memberprofile.MemberProfileRepository; +import com.objectcomputing.checkins.exceptions.BadArgException; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; @@ -45,7 +46,7 @@ public CompensationHistory() { } public void load(MemberProfileRepository memberProfileRepository, - ByteBuffer dataSource) throws IOException { + ByteBuffer dataSource) throws IOException, BadArgException { ByteArrayInputStream stream = new ByteArrayInputStream(dataSource.array()); InputStreamReader input = new InputStreamReader(stream); CSVParser csvParser = CSVFormat.RFC4180 @@ -57,8 +58,8 @@ public void load(MemberProfileRepository memberProfileRepository, .parse(input); history.clear(); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M/d/yyyy"); for (CSVRecord csvRecord : csvParser) { + try { String emailAddress = csvRecord.get("emailAddress"); Optional memberProfile = memberProfileRepository.findByWorkEmail(emailAddress); @@ -77,6 +78,9 @@ public void load(MemberProfileRepository memberProfileRepository, } else { LOG.error("Unable to find a profile for " + emailAddress); } + } catch(IllegalArgumentException ex) { + throw new BadArgException("Unable to parse the compensation history"); + } } } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java index 1fe18626a..d4daee26a 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java @@ -3,6 +3,7 @@ import com.objectcomputing.checkins.exceptions.NotFoundException; import com.objectcomputing.checkins.services.memberprofile.MemberProfile; import com.objectcomputing.checkins.services.memberprofile.MemberProfileRepository; +import com.objectcomputing.checkins.exceptions.BadArgException; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; @@ -46,7 +47,7 @@ public CurrentInformation() { } public void load(MemberProfileRepository memberProfileRepository, - ByteBuffer dataSource) throws IOException { + ByteBuffer dataSource) throws IOException, BadArgException { ByteArrayInputStream stream = new ByteArrayInputStream(dataSource.array()); InputStreamReader input = new InputStreamReader(stream); CSVParser csvParser = CSVFormat.RFC4180 @@ -60,6 +61,7 @@ public void load(MemberProfileRepository memberProfileRepository, information.clear(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M/d/yyyy"); for (CSVRecord csvRecord : csvParser) { + try { String emailAddress = csvRecord.get("emailAddress"); Optional memberProfile = memberProfileRepository.findByWorkEmail(emailAddress); @@ -77,6 +79,9 @@ public void load(MemberProfileRepository memberProfileRepository, } else { LOG.error("Unable to find a profile for " + emailAddress); } + } catch(IllegalArgumentException ex) { + throw new BadArgException("Unable to parse the current information"); + } } } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/PositionHistory.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/PositionHistory.java index 1776aa92f..664c0e1bd 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/PositionHistory.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/PositionHistory.java @@ -2,6 +2,7 @@ import com.objectcomputing.checkins.services.memberprofile.MemberProfile; import com.objectcomputing.checkins.services.memberprofile.MemberProfileRepository; +import com.objectcomputing.checkins.exceptions.BadArgException; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; @@ -45,7 +46,7 @@ public PositionHistory() { } public void load(MemberProfileRepository memberProfileRepository, - ByteBuffer dataSource) throws IOException { + ByteBuffer dataSource) throws IOException, BadArgException { ByteArrayInputStream stream = new ByteArrayInputStream(dataSource.array()); InputStreamReader input = new InputStreamReader(stream); CSVParser csvParser = CSVFormat.RFC4180 @@ -58,6 +59,7 @@ public void load(MemberProfileRepository memberProfileRepository, history.clear(); for (CSVRecord csvRecord : csvParser) { + try { String emailAddress = csvRecord.get("emailAddress"); Optional memberProfile = memberProfileRepository.findByWorkEmail(emailAddress); @@ -75,6 +77,9 @@ public void load(MemberProfileRepository memberProfileRepository, } else { LOG.error("Unable to find a profile for " + emailAddress); } + } catch(IllegalArgumentException ex) { + throw new BadArgException("Unable to parse the position history"); + } } } From 1e7a6263f916c2d575d7fcc1c3c1879fac473855 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Mon, 9 Sep 2024 14:59:42 -0500 Subject: [PATCH 41/55] Take the first element of the set of employee hours. --- .../services/reports/ReportDataCollation.java | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java index be043c6ab..e96550b01 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java @@ -193,21 +193,18 @@ public ReportHours getReportHours() { float ptoHours = 0; float overtimeHours = 0; float billableUtilization = 0; - for (EmployeeHours hours: employeeHours) { - LocalDate date = hours.getAsOfDate(); - if ((date.isEqual(startDate) || - date.isAfter(startDate)) && date.isBefore(endDate)) { - contributionHours += hours.getContributionHours(); - ptoHours += hours.getPtoHours(); + if (!employeeHours.isEmpty()) { + EmployeeHours hours = employeeHours.iterator().next(); + contributionHours = hours.getContributionHours(); + ptoHours = hours.getPtoHours(); - Float otw = hours.getOvertimeWorked(); - if (otw != null) { - overtimeHours += otw; - } - Float bu = hours.getBillableUtilization(); - if (bu != null) { - billableUtilization += bu; - } + Float otw = hours.getOvertimeWorked(); + if (otw != null) { + overtimeHours = otw; + } + Float bu = hours.getBillableUtilization(); + if (bu != null) { + billableUtilization = bu; } } return new ReportHours(contributionHours, ptoHours, From fc77f50be210d869679629fd434188c36522b20f Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Tue, 10 Sep 2024 08:10:54 -0500 Subject: [PATCH 42/55] Synchronize the data services impl methods that modify the stored data files to avoid accidental loss of data during upload. --- .../services/reports/ReportDataController.java | 2 +- .../services/reports/ReportDataServicesImpl.java | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java index 1d192f6d8..49b14fb23 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java @@ -76,7 +76,7 @@ public ReportDataController(ReportDataServices reportDataServices, @Post(uri="/upload", consumes = MediaType.MULTIPART_FORM_DATA) @RequiredPermission(Permission.CAN_CREATE_MERIT_REPORT) - public Mono>> upload( + public Mono>> uploadDataFiles( @Part("comp") Publisher comp, @Part("curr") Publisher curr, @Part("pos") Publisher pos) { diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java index c40611064..9b68347fb 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java @@ -51,8 +51,10 @@ public ReportDataServicesImpl( } + // Synchronized since we can upload multiple files at one time and there + // is an expiration timer modifying the map too. @Override - public void store(DataType dataType, CompletedFileUpload file) throws IOException { + public synchronized void store(DataType dataType, CompletedFileUpload file) throws IOException { MemberProfile currentUser = currentUserServices.getCurrentUser(); boolean isAdmin = currentUserServices.isAdmin(); validate(!isAdmin, NOT_AUTHORIZED_MSG); @@ -96,10 +98,12 @@ public ByteBuffer get(DataType dataType) throws NotFoundException { " Document does not exist"); } - /// Check periodically to see if any data has expired. If it has, remove - /// it. + // Synchronized since we can upload multiple files at one time and data + // could expire symultaneously as well. + // Check periodically to see if any data has expired. If it has, remove + // it. @Override - public void run() { + public synchronized void run() { long current = (new Date()).getTime(); for (Map.Entry entry : storedUploads.entrySet()) { Stored value = entry.getValue(); From 8628863d55110b158e88901e37b06d6d34afea05 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Tue, 10 Sep 2024 08:31:55 -0500 Subject: [PATCH 43/55] Minor cleanup and comments. --- .../checkins/services/file/FileServicesImpl.java | 3 +++ .../checkins/services/reports/ReportDataCollation.java | 10 ++++++++++ .../services/reports/ReportDataServicesImpl.java | 3 +-- .../checkins/services/reports/ReportHours.java | 3 +-- .../services/reports/ReportDataControllerTest.java | 1 + 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/file/FileServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/file/FileServicesImpl.java index e9c38a53c..79aec8c4a 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/file/FileServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/file/FileServicesImpl.java @@ -212,6 +212,9 @@ public FileInfoDTO uploadFile(@NotNull UUID checkInID, @NotNull CompletedFileUpl } } + /// Upload a Markdown document to the specified directory and copy it to + /// a Google document, which results in an automatic conversion from + /// Markdown to the Google Doc format. public FileInfoDTO uploadDocument(String directoryName, String name, String text) { final String GOOGLE_DOC_TYPE = "application/vnd.google-apps.document"; MemberProfile currentUser = currentUserServices.getCurrentUser(); diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java index e96550b01..ded89a6c7 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java @@ -36,6 +36,8 @@ import java.nio.ByteBuffer; import java.io.IOException; +/// Collates all of the data necessary to generate a Merit Report. It uses +/// files uploaded by the user, as well as data stored in the database. public class ReportDataCollation { private static final Logger LOG = LoggerFactory.getLogger(ReportDataCollation.class); @@ -143,6 +145,7 @@ public MemberProfile getMemberProfile() { ); } + /// Get the compensation history for the designated member. public List getCompensationHistory() { try { ByteBuffer buffer = reportDataServices.get( @@ -153,6 +156,7 @@ public List getCompensationHistory() { return compensationHistory.getHistory(memberId); } + /// Get the current information for the designated member. public CurrentInformation.Information getCurrentInformation() { try { ByteBuffer buffer = reportDataServices.get( @@ -163,6 +167,7 @@ public CurrentInformation.Information getCurrentInformation() { return currentInformation.getInformation(memberId); } + /// Get the position history for the designated member. public List getPositionHistory() { try { ByteBuffer buffer = reportDataServices.get( @@ -173,18 +178,23 @@ public List getPositionHistory() { return positionHistory.getHistory(memberId); } + /// Get the self reviews for the designated member. public List getSelfReviews() { return getFeedbackType(FeedbackType.selfReviews); } + /// Get the reviews for the designated member. public List getReviews() { return getFeedbackType(FeedbackType.reviews); } + /// Get the feedback for the designated member. public List getFeedback() { return getFeedbackType(FeedbackType.feedback); } + /// Get the employee hours for the designated member. This only returns + /// the first entry found for the member. public ReportHours getReportHours() { MemberProfile memberProfile = getMemberProfile(); Set employeeHours = diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java index 9b68347fb..fa0cd0566 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java @@ -43,8 +43,7 @@ public Stored() { private final long expireCheck = 10*60*1000; private final long expiration = 60*60*1000; - public ReportDataServicesImpl( - CurrentUserServices currentUserServices) { + public ReportDataServicesImpl(CurrentUserServices currentUserServices) { this.currentUserServices = currentUserServices; timer.scheduleAtFixedRate(this, new Date(), expireCheck); diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportHours.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportHours.java index 119aa9f77..dea49a867 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportHours.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportHours.java @@ -1,11 +1,10 @@ package com.objectcomputing.checkins.services.reports; -import io.micronaut.core.annotation.Introspected; + import lombok.AllArgsConstructor; import lombok.Getter; @AllArgsConstructor @Getter -@Introspected class ReportHours { private final float contributionHours; private final float ptoHours; diff --git a/server/src/test/java/com/objectcomputing/checkins/services/reports/ReportDataControllerTest.java b/server/src/test/java/com/objectcomputing/checkins/services/reports/ReportDataControllerTest.java index ec14c9314..f71223b6f 100644 --- a/server/src/test/java/com/objectcomputing/checkins/services/reports/ReportDataControllerTest.java +++ b/server/src/test/java/com/objectcomputing/checkins/services/reports/ReportDataControllerTest.java @@ -101,6 +101,7 @@ void getReportData() throws JsonProcessingException { assertNotNull(first.get("selfReviews")); assertNotNull(first.get("reviews")); assertNotNull(first.get("feedback")); + assertNotNull(first.get("hours")); } @Test From 44f48493c8176cb6d986bbdd6e4572df6560e815 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Tue, 10 Sep 2024 09:07:59 -0500 Subject: [PATCH 44/55] Factor common code out into a CSV processor class. --- .../services/reports/CSVProcessor.java | 58 +++++++++ .../services/reports/CompensationHistory.java | 80 ++++-------- .../services/reports/CurrentInformation.java | 117 ++++++++---------- .../services/reports/PositionHistory.java | 115 ++++++----------- 4 files changed, 171 insertions(+), 199 deletions(-) create mode 100644 server/src/main/java/com/objectcomputing/checkins/services/reports/CSVProcessor.java diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/CSVProcessor.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/CSVProcessor.java new file mode 100644 index 000000000..182926eba --- /dev/null +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/CSVProcessor.java @@ -0,0 +1,58 @@ +package com.objectcomputing.checkins.services.reports; + +import com.objectcomputing.checkins.services.memberprofile.MemberProfileRepository; +import com.objectcomputing.checkins.exceptions.BadArgException; + +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; + +import java.nio.ByteBuffer; +import java.io.ByteArrayInputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.util.List; + +public class CSVProcessor { + + protected CSVProcessor() { + } + + public void load(MemberProfileRepository memberProfileRepository, + ByteBuffer dataSource) throws IOException, + BadArgException { + ByteArrayInputStream stream = new ByteArrayInputStream(dataSource.array()); + InputStreamReader input = new InputStreamReader(stream); + CSVParser csvParser = CSVFormat.RFC4180 + .builder() + .setHeader().setSkipHeaderRecord(true) + .setIgnoreSurroundingSpaces(true) + .setNullString("") + .build() + .parse(input); + loadImpl(memberProfileRepository, csvParser); + } + + protected void loadImpl(MemberProfileRepository memberProfileRepository, + CSVParser csvParser) throws BadArgException {} + + protected LocalDate parseDate(String date) { + List formatStrings = List.of("yyyy", "M/d/yyyy"); + for(String format: formatStrings) { + try { + return LocalDate.parse(date, + new DateTimeFormatterBuilder() + .appendPattern(format) + .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1) + .parseDefaulting(ChronoField.DAY_OF_MONTH, 1) + .toFormatter()); + } catch(DateTimeParseException ex) { + } + } + return null; + } +} diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java index 614b54f09..1aa337490 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java @@ -4,7 +4,6 @@ import com.objectcomputing.checkins.services.memberprofile.MemberProfileRepository; import com.objectcomputing.checkins.exceptions.BadArgException; -import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; @@ -14,22 +13,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.nio.ByteBuffer; -import java.io.ByteArrayInputStream; -import java.io.InputStreamReader; -import java.io.IOException; import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; -import java.time.format.DateTimeParseException; -import java.time.temporal.ChronoField; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.Optional; import java.util.stream.Collectors; -public class CompensationHistory { +public class CompensationHistory extends CSVProcessor { @AllArgsConstructor @Getter @@ -45,64 +36,39 @@ public class Compensation { public CompensationHistory() { } - public void load(MemberProfileRepository memberProfileRepository, - ByteBuffer dataSource) throws IOException, BadArgException { - ByteArrayInputStream stream = new ByteArrayInputStream(dataSource.array()); - InputStreamReader input = new InputStreamReader(stream); - CSVParser csvParser = CSVFormat.RFC4180 - .builder() - .setHeader().setSkipHeaderRecord(true) - .setIgnoreSurroundingSpaces(true) - .setNullString("") - .build() - .parse(input); - - history.clear(); - for (CSVRecord csvRecord : csvParser) { - try { - String emailAddress = csvRecord.get("emailAddress"); - Optional memberProfile = - memberProfileRepository.findByWorkEmail(emailAddress); - if (memberProfile.isPresent()) { - LocalDate date = parseDate(csvRecord.get("startDate")); - if (date == null) { - LOG.error("Unable to parse date: " + csvRecord.get("startDate")); - } else { - Compensation comp = new Compensation( + @Override + protected void loadImpl(MemberProfileRepository memberProfileRepository, + CSVParser csvParser) throws BadArgException { + history.clear(); + for (CSVRecord csvRecord : csvParser) { + try { + String emailAddress = csvRecord.get("emailAddress"); + Optional memberProfile = + memberProfileRepository.findByWorkEmail(emailAddress); + if (memberProfile.isPresent()) { + LocalDate date = parseDate(csvRecord.get("startDate")); + if (date == null) { + LOG.error("Unable to parse date: " + csvRecord.get("startDate")); + } else { + Compensation comp = new Compensation( memberProfile.get().getId(), date, Float.parseFloat(csvRecord.get("compensation") .replaceAll("[^\\d\\.,]", ""))); - history.add(comp); - } - } else { - LOG.error("Unable to find a profile for " + emailAddress); + history.add(comp); } - } catch(IllegalArgumentException ex) { - throw new BadArgException("Unable to parse the compensation history"); + } else { + LOG.error("Unable to find a profile for " + emailAddress); } + } catch(IllegalArgumentException ex) { + throw new BadArgException("Unable to parse the compensation history"); } + } } public List getHistory(UUID memberId) { - return history.stream() + return history.stream() .filter(entry -> entry.getMemberId().equals(memberId)) .collect(Collectors.toList()); } - - private LocalDate parseDate(String date) { - List formatStrings = List.of("yyyy", "M/d/yyyy"); - for(String format: formatStrings) { - try { - return LocalDate.parse(date, - new DateTimeFormatterBuilder() - .appendPattern(format) - .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1) - .parseDefaulting(ChronoField.DAY_OF_MONTH, 1) - .toFormatter()); - } catch(DateTimeParseException ex) { - } - } - return null; - } } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java index d4daee26a..27f5e5c3b 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java @@ -5,7 +5,6 @@ import com.objectcomputing.checkins.services.memberprofile.MemberProfileRepository; import com.objectcomputing.checkins.exceptions.BadArgException; -import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; @@ -15,85 +14,69 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.nio.ByteBuffer; -import java.io.ByteArrayInputStream; -import java.io.InputStreamReader; -import java.io.IOException; import java.time.LocalDate; -import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.Optional; import java.util.stream.Collectors; -public class CurrentInformation { +public class CurrentInformation extends CSVProcessor { - @AllArgsConstructor - @Getter - public class Information { - private UUID memberId; - private float salary; - private String range; - private String nationalRange; - private String biography; - private String commitments; - } - - private static final Logger LOG = LoggerFactory.getLogger(CurrentInformation.class); - private List information = new ArrayList(); + @AllArgsConstructor + @Getter + public class Information { + private UUID memberId; + private float salary; + private String range; + private String nationalRange; + private String biography; + private String commitments; + } - public CurrentInformation() { - } + private static final Logger LOG = LoggerFactory.getLogger(CurrentInformation.class); + private List information = new ArrayList(); - public void load(MemberProfileRepository memberProfileRepository, - ByteBuffer dataSource) throws IOException, BadArgException { - ByteArrayInputStream stream = new ByteArrayInputStream(dataSource.array()); - InputStreamReader input = new InputStreamReader(stream); - CSVParser csvParser = CSVFormat.RFC4180 - .builder() - .setHeader().setSkipHeaderRecord(true) - .setIgnoreSurroundingSpaces(true) - .setNullString("") - .build() - .parse(input); + public CurrentInformation() { + } - information.clear(); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M/d/yyyy"); - for (CSVRecord csvRecord : csvParser) { - try { - String emailAddress = csvRecord.get("emailAddress"); - Optional memberProfile = - memberProfileRepository.findByWorkEmail(emailAddress); - if (memberProfile.isPresent()) { - Information comp = new Information( - memberProfile.get().getId(), - Float.parseFloat(csvRecord.get("salary") - .replaceAll("[^\\d\\.,]", "")), - csvRecord.get("range"), - csvRecord.get("nationalRange"), - csvRecord.get("biography"), - csvRecord.get("commitments") - ); - information.add(comp); - } else { - LOG.error("Unable to find a profile for " + emailAddress); - } - } catch(IllegalArgumentException ex) { - throw new BadArgException("Unable to parse the current information"); - } + protected void loadImpl(MemberProfileRepository memberProfileRepository, + CSVParser csvParser) throws BadArgException { + information.clear(); + for (CSVRecord csvRecord : csvParser) { + try { + String emailAddress = csvRecord.get("emailAddress"); + Optional memberProfile = + memberProfileRepository.findByWorkEmail(emailAddress); + if (memberProfile.isPresent()) { + Information comp = new Information( + memberProfile.get().getId(), + Float.parseFloat(csvRecord.get("salary") + .replaceAll("[^\\d\\.,]", "")), + csvRecord.get("range"), + csvRecord.get("nationalRange"), + csvRecord.get("biography"), + csvRecord.get("commitments") + ); + information.add(comp); + } else { + LOG.error("Unable to find a profile for " + emailAddress); } + } catch(IllegalArgumentException ex) { + throw new BadArgException("Unable to parse the current information"); + } } + } - public Information getInformation(UUID memberId) { - // There should only be one entry per member. - List list = information.stream() - .filter(entry -> entry.getMemberId().equals(memberId)) - .collect(Collectors.toList()); - if (list.isEmpty()) { - throw new NotFoundException("Current Information not found for member: " + memberId); - } else { - return list.get(0); - } + public Information getInformation(UUID memberId) { + // There should only be one entry per member. + List list = information.stream() + .filter(entry -> entry.getMemberId().equals(memberId)) + .collect(Collectors.toList()); + if (list.isEmpty()) { + throw new NotFoundException("Current Information not found for member: " + memberId); + } else { + return list.get(0); } + } } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/PositionHistory.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/PositionHistory.java index 664c0e1bd..5ad7ee73b 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/PositionHistory.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/PositionHistory.java @@ -4,7 +4,6 @@ import com.objectcomputing.checkins.services.memberprofile.MemberProfileRepository; import com.objectcomputing.checkins.exceptions.BadArgException; -import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; @@ -14,94 +13,60 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.nio.ByteBuffer; -import java.io.ByteArrayInputStream; -import java.io.InputStreamReader; -import java.io.IOException; import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; -import java.time.temporal.ChronoField; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.Optional; import java.util.stream.Collectors; -import java.time.format.DateTimeParseException; -public class PositionHistory { +public class PositionHistory extends CSVProcessor { - @AllArgsConstructor - @Getter - public class Position { - private UUID memberId; - private LocalDate date; - private String title; - } - - private static final Logger LOG = LoggerFactory.getLogger(PositionHistory.class); - private List history = new ArrayList(); + @AllArgsConstructor + @Getter + public class Position { + private UUID memberId; + private LocalDate date; + private String title; + } - public PositionHistory() { - } + private static final Logger LOG = LoggerFactory.getLogger(PositionHistory.class); + private List history = new ArrayList(); - public void load(MemberProfileRepository memberProfileRepository, - ByteBuffer dataSource) throws IOException, BadArgException { - ByteArrayInputStream stream = new ByteArrayInputStream(dataSource.array()); - InputStreamReader input = new InputStreamReader(stream); - CSVParser csvParser = CSVFormat.RFC4180 - .builder() - .setHeader().setSkipHeaderRecord(true) - .setIgnoreSurroundingSpaces(true) - .setNullString("") - .build() - .parse(input); + public PositionHistory() { + } - history.clear(); - for (CSVRecord csvRecord : csvParser) { - try { - String emailAddress = csvRecord.get("emailAddress"); - Optional memberProfile = - memberProfileRepository.findByWorkEmail(emailAddress); - if (memberProfile.isPresent()) { - LocalDate date = parseDate(csvRecord.get("date")); - if (date == null) { - LOG.error("Unable to parse date: " + csvRecord.get("date")); - } else { - Position position = new Position( - memberProfile.get().getId(), - date, - csvRecord.get("title")); - history.add(position); - } - } else { - LOG.error("Unable to find a profile for " + emailAddress); - } - } catch(IllegalArgumentException ex) { - throw new BadArgException("Unable to parse the position history"); + protected void loadImpl(MemberProfileRepository memberProfileRepository, + CSVParser csvParser) throws BadArgException { + history.clear(); + for (CSVRecord csvRecord : csvParser) { + try { + String emailAddress = csvRecord.get("emailAddress"); + Optional memberProfile = + memberProfileRepository.findByWorkEmail(emailAddress); + if (memberProfile.isPresent()) { + LocalDate date = parseDate(csvRecord.get("date")); + if (date == null) { + LOG.error("Unable to parse date: " + csvRecord.get("date")); + } else { + Position position = new Position( + memberProfile.get().getId(), + date, + csvRecord.get("title")); + history.add(position); } + } else { + LOG.error("Unable to find a profile for " + emailAddress); } - } - - private LocalDate parseDate(String date) { - List formatStrings = List.of("yyyy", "M/d/yyyy"); - for(String format: formatStrings) { - try { - return LocalDate.parse(date, - new DateTimeFormatterBuilder() - .appendPattern(format) - .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1) - .parseDefaulting(ChronoField.DAY_OF_MONTH, 1) - .toFormatter()); - } catch(DateTimeParseException ex) { - } + } catch(IllegalArgumentException ex) { + throw new BadArgException("Unable to parse the position history"); } - return null; } + } - public List getHistory(UUID memberId) { - return history.stream() - .filter(entry -> entry.getMemberId().equals(memberId)) - .collect(Collectors.toList()); - } + public List getHistory(UUID memberId) { + return history.stream() + .filter(entry -> entry.getMemberId().equals(memberId)) + .collect(Collectors.toList()); + } } From 1e337d9f01d87bf8429f436a0f043204ee44f389 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Tue, 10 Sep 2024 09:10:12 -0500 Subject: [PATCH 45/55] Added missing override annotation. --- .../checkins/services/reports/CurrentInformation.java | 1 + .../checkins/services/reports/PositionHistory.java | 1 + 2 files changed, 2 insertions(+) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java index 27f5e5c3b..6d4427eb1 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java @@ -40,6 +40,7 @@ public class Information { public CurrentInformation() { } + @Override protected void loadImpl(MemberProfileRepository memberProfileRepository, CSVParser csvParser) throws BadArgException { information.clear(); diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/PositionHistory.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/PositionHistory.java index 5ad7ee73b..864e7603e 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/PositionHistory.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/PositionHistory.java @@ -36,6 +36,7 @@ public class Position { public PositionHistory() { } + @Override protected void loadImpl(MemberProfileRepository memberProfileRepository, CSVParser csvParser) throws BadArgException { history.clear(); From 3030207781bb7c701b058716f83352c2cdcbef2c Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Tue, 10 Sep 2024 09:38:06 -0500 Subject: [PATCH 46/55] Log errors while trashing documents that about to be replaced. --- .../checkins/services/file/FileServicesImpl.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/file/FileServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/file/FileServicesImpl.java index 79aec8c4a..505f68d56 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/file/FileServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/file/FileServicesImpl.java @@ -273,7 +273,9 @@ public FileInfoDTO uploadDocument(String directoryName, String name, String text .setSupportsAllDrives(true) .execute(); } catch (GoogleJsonResponseException e) { + LOG.error("Error while trashing " + file.getName(), e); } catch (IOException e) { + LOG.error("Error while trashing " + file.getName(), e); } } } From c00f63c1fb287bdac12e84a1eb8b4f8a36ad6597 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Mon, 16 Sep 2024 09:25:42 -0500 Subject: [PATCH 47/55] Converted classes to records. --- .../checkins/services/reports/ReportHours.java | 17 ++++++----------- .../checkins/services/reports/ReportKudos.java | 15 +++++---------- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportHours.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportHours.java index dea49a867..11b27a646 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportHours.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportHours.java @@ -1,14 +1,9 @@ package com.objectcomputing.checkins.services.reports; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@AllArgsConstructor -@Getter -class ReportHours { - private final float contributionHours; - private final float ptoHours; - private final float overtimeHours; - private final float billableUtilization; +record ReportHours( + float contributionHours, + float ptoHours, + float overtimeHours, + float billableUtilization +) { } - diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportKudos.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportKudos.java index df49bee44..064bc743d 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportKudos.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportKudos.java @@ -1,15 +1,10 @@ package com.objectcomputing.checkins.services.reports; -import lombok.AllArgsConstructor; -import lombok.Getter; - import java.time.LocalDate; -@AllArgsConstructor -@Getter -class ReportKudos { - private final LocalDate dateCreated; - private final String message; - private final String sender; +record ReportKudos( + LocalDate dateCreated, + String message, + String sender +) { } - From 908f1ee80c95b1e4574295a03f6be5ab5b42df6d Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Mon, 16 Sep 2024 11:19:21 -0500 Subject: [PATCH 48/55] Simplifications from review. --- .../services/reports/CurrentInformation.java | 38 +++++++------------ 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java index 6d4427eb1..2f9aff6c0 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java @@ -8,9 +8,6 @@ import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; -import lombok.AllArgsConstructor; -import lombok.Getter; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,26 +16,21 @@ import java.util.List; import java.util.UUID; import java.util.Optional; -import java.util.stream.Collectors; public class CurrentInformation extends CSVProcessor { - @AllArgsConstructor - @Getter - public class Information { - private UUID memberId; - private float salary; - private String range; - private String nationalRange; - private String biography; - private String commitments; + public record Information( + UUID memberId, + float salary, + String range, + String nationalRange, + String biography, + String commitments + ) { } private static final Logger LOG = LoggerFactory.getLogger(CurrentInformation.class); - private List information = new ArrayList(); - - public CurrentInformation() { - } + private List information = new ArrayList<>(); @Override protected void loadImpl(MemberProfileRepository memberProfileRepository, @@ -71,13 +63,9 @@ protected void loadImpl(MemberProfileRepository memberProfileRepository, public Information getInformation(UUID memberId) { // There should only be one entry per member. - List list = information.stream() - .filter(entry -> entry.getMemberId().equals(memberId)) - .collect(Collectors.toList()); - if (list.isEmpty()) { - throw new NotFoundException("Current Information not found for member: " + memberId); - } else { - return list.get(0); - } + return information.stream() + .filter(entry -> entry.memberId().equals(memberId)) + .findFirst() + .orElseThrow(() -> new NotFoundException("Current Information not found for member: " + memberId)); } } From 7601c96ae4f0ea0be1b34eeda6e25874af90c2b3 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Mon, 16 Sep 2024 14:55:50 -0500 Subject: [PATCH 49/55] Corrected handling of feedback requests relating to review period and submit date. --- .../services/reports/ReportDataCollation.java | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java index ded89a6c7..969aa5aea 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataCollation.java @@ -123,8 +123,7 @@ public List getKudos() { .orElse(null); if (kudos != null) { LocalDate created = kudos.getDateCreated(); - if ((created.isEqual(startDate) || - created.isAfter(startDate)) && created.isBefore(endDate)) { + if (dateInRange(created, startDate, endDate)) { MemberProfile senderProfile = memberProfileRepository.findById(kudos.getSenderId()).orElse(null); String sender = senderProfile == null ? @@ -229,16 +228,23 @@ private List getFeedbackType(FeedbackType type) { LocalDateRange dateRange = getDateRange(); List requests = feedbackRequestServices.findByValues(null, memberId, null, - dateRange.start, - null, null, null); + null, null, null, null); // Iterate over each request and find the template. Determine the purpose // of the template. ReviewPeriod reviewPeriod = reviewPeriodServices.findById(reviewPeriodId); Map templates = new HashMap(); for (FeedbackRequest request: requests) { - if (request.getReviewPeriodId() == null && - !templates.containsKey(request.getTemplateId())) { + // Make sure we haven't already considered this template. + // Also, require that the request either be directly associated with + // our review period or that the request was submitted within the time + // range of our review period. + if (!templates.containsKey(request.getTemplateId()) && + ((request.getReviewPeriodId() != null && + request.getReviewPeriodId().equals(reviewPeriod.getId())) || + (request.getSubmitDate() != null && + dateInRange(request.getSubmitDate(), dateRange.start, + dateRange.end)))) { try { FeedbackTemplate template = feedbackTemplateServices.getById(request.getTemplateId()); @@ -332,4 +338,10 @@ private LocalDateRange getDateRange() { return new LocalDateRange(startDate, endDate); } } + + private boolean dateInRange(LocalDate date, + LocalDate startDate, LocalDate endDate) { + return (date.isEqual(startDate) || + date.isAfter(startDate)) && date.isBefore(endDate); + } } From 1a870244e87abc716c3400cb278782ca8661603e Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Tue, 17 Sep 2024 08:00:52 -0500 Subject: [PATCH 50/55] Added more data and validation of returned jSON. --- .../resources/db/dev/R__Load_testing_data.sql | 32 ++--- .../fixture/EmployeeHoursFixture.java | 4 + .../fixture/FeedbackAnswerFixture.java | 4 + .../services/fixture/ReviewPeriodFixture.java | 11 ++ .../reports/ReportDataControllerTest.java | 109 +++++++++++++++++- .../reports/data/currentInformation.csv | 2 +- 6 files changed, 141 insertions(+), 21 deletions(-) diff --git a/server/src/main/resources/db/dev/R__Load_testing_data.sql b/server/src/main/resources/db/dev/R__Load_testing_data.sql index e90c3f199..698aacf19 100644 --- a/server/src/main/resources/db/dev/R__Load_testing_data.sql +++ b/server/src/main/resources/db/dev/R__Load_testing_data.sql @@ -1272,33 +1272,39 @@ INSERT INTO feedback_requests VALUES ('e2af1c96-a593-48c2-b9e0-a00193a070c7', '8d75c07e-6adc-437a-8659-7dd953ce6600', '1c813446-c65a-4f49-b980-0193f7bfff8c', 'dfe2f986-fac0-11eb-9a03-0242ac130003','18ef2032-c264-411e-a8e1-ddda9a714bae', '2021-08-01', '2021-08-05', '2021-08-02', 'submitted'); +-- CAE - Review Periods +INSERT INTO review_periods +(id, name, review_status, review_template_id, self_review_template_id, launch_date, self_review_close_date, close_date, period_start_date, period_end_date) +VALUES + ('12345678-e29c-4cf4-9ea4-6baa09405c57', 'Review Period 1', 'CLOSED', 'd1e94b60-47c4-4945-87d1-4dc88f088e57', '926a37a4-4ded-4633-8900-715b0383aecc', '2024-09-01', '2024-09-02', '2024-09-03', '2024-01-01', '2024-12-31'); + -- CAE - Self-Review feedback request, Creator: Big Boss INSERT INTO feedback_requests -(id, creator_id, requestee_id, recipient_id, template_id, send_date, due_date, submit_date, status) +(id, creator_id, requestee_id, recipient_id, template_id, send_date, due_date, submit_date, status, review_period_id) VALUES -('98390c09-7121-110a-bfee-9380a470a7ef', '72655c4f-1fb8-4514-b31e-7f7e19fa9bd7', 'c7406157-a38f-4d48-aaed-04018d846727', 'c7406157-a38f-4d48-aaed-04018d846727', '926a37a4-4ded-4633-8900-715b0383aecc', '2024-09-04', '2024-09-30', '2024-09-05', 'sent'); +('98390c09-7121-110a-bfee-9380a470a7ef', '72655c4f-1fb8-4514-b31e-7f7e19fa9bd7', 'c7406157-a38f-4d48-aaed-04018d846727', 'c7406157-a38f-4d48-aaed-04018d846727', '926a37a4-4ded-4633-8900-715b0383aecc', '2024-09-04', '2024-09-30', '2024-09-05', 'sent', '12345678-e29c-4cf4-9ea4-6baa09405c57'); -- CAE - Review feedback request, Creator: Big Boss INSERT INTO feedback_requests -(id, creator_id, requestee_id, recipient_id, template_id, send_date, due_date, submit_date, status) +(id, creator_id, requestee_id, recipient_id, template_id, send_date, due_date, submit_date, status, review_period_id) VALUES -('98390c09-7121-110a-bfee-9380a470a7f0', '72655c4f-1fb8-4514-b31e-7f7e19fa9bd7', 'c7406157-a38f-4d48-aaed-04018d846727', '6207b3fd-042d-49aa-9e28-dcc04f537c2d', 'd1e94b60-47c4-4945-87d1-4dc88f088e57', '2024-09-04', '2024-09-30', '2024-09-05', 'sent'); +('98390c09-7121-110a-bfee-9380a470a7f0', '72655c4f-1fb8-4514-b31e-7f7e19fa9bd7', 'c7406157-a38f-4d48-aaed-04018d846727', '6207b3fd-042d-49aa-9e28-dcc04f537c2d', 'd1e94b60-47c4-4945-87d1-4dc88f088e57', '2024-09-04', '2024-09-30', '2024-09-05', 'sent', '12345678-e29c-4cf4-9ea4-6baa09405c57'); INSERT INTO feedback_requests -(id, creator_id, requestee_id, recipient_id, template_id, send_date, due_date, submit_date, status) +(id, creator_id, requestee_id, recipient_id, template_id, send_date, due_date, submit_date, status, review_period_id) VALUES -('98390c09-7121-110a-bfee-9380a470a7f3', '72655c4f-1fb8-4514-b31e-7f7e19fa9bd7', 'c7406157-a38f-4d48-aaed-04018d846727', 'dfe2f986-fac0-11eb-9a03-0242ac130003', 'd1e94b60-47c4-4945-87d1-4dc88f088e57', '2024-09-04', '2024-09-30', '2024-09-05', 'sent'); +('98390c09-7121-110a-bfee-9380a470a7f3', '72655c4f-1fb8-4514-b31e-7f7e19fa9bd7', 'c7406157-a38f-4d48-aaed-04018d846727', 'dfe2f986-fac0-11eb-9a03-0242ac130003', 'd1e94b60-47c4-4945-87d1-4dc88f088e57', '2024-09-04', '2024-09-30', '2024-09-05', 'sent', '12345678-e29c-4cf4-9ea4-6baa09405c57'); -- CAE - Feedback request, Creator: Big Boss INSERT INTO feedback_requests -(id, creator_id, requestee_id, recipient_id, template_id, send_date, due_date, submit_date, status) +(id, creator_id, requestee_id, recipient_id, template_id, send_date, due_date, submit_date, status, review_period_id) VALUES -('98390c09-7121-110a-bfee-9380a470a7f1', '72655c4f-1fb8-4514-b31e-7f7e19fa9bd7', 'c7406157-a38f-4d48-aaed-04018d846727', '6207b3fd-042d-49aa-9e28-dcc04f537c2d', '1c8bc142-c447-4889-986e-42ab177da683', '2024-09-04', '2024-09-30', '2024-09-05', 'sent'); +('98390c09-7121-110a-bfee-9380a470a7f1', '72655c4f-1fb8-4514-b31e-7f7e19fa9bd7', 'c7406157-a38f-4d48-aaed-04018d846727', '6207b3fd-042d-49aa-9e28-dcc04f537c2d', '1c8bc142-c447-4889-986e-42ab177da683', '2024-09-04', '2024-09-30', '2024-09-05', 'sent', '12345678-e29c-4cf4-9ea4-6baa09405c57'); INSERT INTO feedback_requests -(id, creator_id, requestee_id, recipient_id, template_id, send_date, due_date, submit_date, status) +(id, creator_id, requestee_id, recipient_id, template_id, send_date, due_date, submit_date, status, review_period_id) VALUES -('98390c09-7121-110a-bfee-9380a470a7f2', '72655c4f-1fb8-4514-b31e-7f7e19fa9bd7', 'c7406157-a38f-4d48-aaed-04018d846727', 'dfe2f986-fac0-11eb-9a03-0242ac130003', '1c8bc142-c447-4889-986e-42ab177da683', '2024-09-04', '2024-09-30', '2024-09-05', 'sent'); +('98390c09-7121-110a-bfee-9380a470a7f2', '72655c4f-1fb8-4514-b31e-7f7e19fa9bd7', 'c7406157-a38f-4d48-aaed-04018d846727', 'dfe2f986-fac0-11eb-9a03-0242ac130003', '1c8bc142-c447-4889-986e-42ab177da683', '2024-09-04', '2024-09-30', '2024-09-05', 'sent', '12345678-e29c-4cf4-9ea4-6baa09405c57'); ---- Creator: Big Boss INSERT INTO feedback_requests @@ -1935,9 +1941,3 @@ INSERT INTO role_documentation VALUES ('d03f5f0b-e29c-4cf4-9ea4-6baa09405c56', 'b553d4c0-9b7a-4691-8fe0-e3bdda4f67ae', 3); --- CAE - Review Periods -INSERT INTO review_periods -(id, name, review_status, review_template_id, self_review_template_id, launch_date, self_review_close_date, close_date, period_start_date, period_end_date) -VALUES - ('12345678-e29c-4cf4-9ea4-6baa09405c57', 'Review Period 1', 'CLOSED', 'd1e94b60-47c4-4945-87d1-4dc88f088e57', '926a37a4-4ded-4633-8900-715b0383aecc', '2024-09-01', '2024-09-02', '2024-09-03', '2024-01-01', '2024-12-31'); - diff --git a/server/src/test/java/com/objectcomputing/checkins/services/fixture/EmployeeHoursFixture.java b/server/src/test/java/com/objectcomputing/checkins/services/fixture/EmployeeHoursFixture.java index 9c2f2aef8..931248e5d 100644 --- a/server/src/test/java/com/objectcomputing/checkins/services/fixture/EmployeeHoursFixture.java +++ b/server/src/test/java/com/objectcomputing/checkins/services/fixture/EmployeeHoursFixture.java @@ -17,4 +17,8 @@ default List createEmployeeHours() throws IOException { return getEmployeeHoursRepository().saveAll(EmployeeHoursCSVHelper.employeeHrsCsv(inputStream)); } } + + default EmployeeHours saveEmployeeHours(EmployeeHours hours) { + return getEmployeeHoursRepository().save(hours); + } } diff --git a/server/src/test/java/com/objectcomputing/checkins/services/fixture/FeedbackAnswerFixture.java b/server/src/test/java/com/objectcomputing/checkins/services/fixture/FeedbackAnswerFixture.java index ae9fe3212..4b8981147 100644 --- a/server/src/test/java/com/objectcomputing/checkins/services/fixture/FeedbackAnswerFixture.java +++ b/server/src/test/java/com/objectcomputing/checkins/services/fixture/FeedbackAnswerFixture.java @@ -10,4 +10,8 @@ default FeedbackAnswer createSampleFeedbackAnswer(UUID questionId, UUID requestI return new FeedbackAnswer("I am doing just fine", questionId, requestId, 0.5); } + default FeedbackAnswer saveSampleFeedbackAnswer(UUID questionId, UUID requestId) { + return getFeedbackAnswerRepository().save(new FeedbackAnswer("I am doing just fine", questionId, requestId, 0.6)); + } + } diff --git a/server/src/test/java/com/objectcomputing/checkins/services/fixture/ReviewPeriodFixture.java b/server/src/test/java/com/objectcomputing/checkins/services/fixture/ReviewPeriodFixture.java index c2f874603..200ceb206 100644 --- a/server/src/test/java/com/objectcomputing/checkins/services/fixture/ReviewPeriodFixture.java +++ b/server/src/test/java/com/objectcomputing/checkins/services/fixture/ReviewPeriodFixture.java @@ -35,4 +35,15 @@ default ReviewPeriod createAClosedReviewPeriod() { LocalDateTime.now().plusDays(2).truncatedTo(ChronoUnit.MILLIS) , LocalDateTime.now().minusDays(30).truncatedTo(ChronoUnit.MILLIS), LocalDateTime.now().minusDays(1).truncatedTo(ChronoUnit.MILLIS))); } + + default ReviewPeriod createAClosedReviewPeriod( + LocalDateTime periodStart, LocalDateTime periodEnd) { + return getReviewPeriodRepository().save( + new ReviewPeriod( + "Period of Closure", ReviewStatus.CLOSED, null, null, + LocalDateTime.now().truncatedTo(ChronoUnit.MILLIS), + LocalDateTime.now().plusDays(1).truncatedTo(ChronoUnit.MILLIS), + LocalDateTime.now().plusDays(2).truncatedTo(ChronoUnit.MILLIS), + periodStart, periodEnd)); + } } diff --git a/server/src/test/java/com/objectcomputing/checkins/services/reports/ReportDataControllerTest.java b/server/src/test/java/com/objectcomputing/checkins/services/reports/ReportDataControllerTest.java index f71223b6f..dd52e793b 100644 --- a/server/src/test/java/com/objectcomputing/checkins/services/reports/ReportDataControllerTest.java +++ b/server/src/test/java/com/objectcomputing/checkins/services/reports/ReportDataControllerTest.java @@ -4,10 +4,24 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.core.JsonProcessingException; +import com.objectcomputing.checkins.services.kudos.Kudos; import com.objectcomputing.checkins.services.TestContainersSuite; +import com.objectcomputing.checkins.services.fixture.KudosFixture; import com.objectcomputing.checkins.services.fixture.RoleFixture; import com.objectcomputing.checkins.services.fixture.MemberProfileFixture; +import com.objectcomputing.checkins.services.fixture.EmployeeHoursFixture; +import com.objectcomputing.checkins.services.fixture.ReviewPeriodFixture; +import com.objectcomputing.checkins.services.fixture.FeedbackTemplateFixture; +import com.objectcomputing.checkins.services.fixture.FeedbackRequestFixture; +import com.objectcomputing.checkins.services.fixture.TemplateQuestionFixture; +import com.objectcomputing.checkins.services.fixture.FeedbackAnswerFixture; import com.objectcomputing.checkins.services.memberprofile.MemberProfile; +import com.objectcomputing.checkins.services.reviews.ReviewPeriod; +import com.objectcomputing.checkins.services.feedback_template.FeedbackTemplate; +import com.objectcomputing.checkins.services.feedback_request.FeedbackRequest; +import com.objectcomputing.checkins.services.feedback_template.template_question.TemplateQuestion; +import com.objectcomputing.checkins.services.feedback_answer.FeedbackAnswer; +import com.objectcomputing.checkins.services.employee_hours.EmployeeHours; import io.micronaut.core.type.Argument; import io.micronaut.http.HttpRequest; @@ -30,6 +44,7 @@ import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; +import java.time.LocalDate; import static com.objectcomputing.checkins.services.role.RoleType.Constants.ADMIN_ROLE; import static com.objectcomputing.checkins.services.role.RoleType.Constants.MEMBER_ROLE; @@ -37,13 +52,20 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; -class ReportDataControllerTest extends TestContainersSuite implements MemberProfileFixture, RoleFixture { +class ReportDataControllerTest extends TestContainersSuite implements MemberProfileFixture, RoleFixture, KudosFixture, ReviewPeriodFixture, FeedbackTemplateFixture, FeedbackRequestFixture, TemplateQuestionFixture, FeedbackAnswerFixture, EmployeeHoursFixture { @Inject @Client("/services/report/data") HttpClient client; + private EmployeeHours employeeHours; + private FeedbackTemplate feedbackTemplate; + private ReviewPeriod reviewPeriod; + private FeedbackRequest feedbackRequest; + private TemplateQuestion questionOne; + private TemplateQuestion questionTwo; private MemberProfile regular; private MemberProfile admin; private final String basePath = "src/test/java/com/objectcomputing/checkins/services/reports/"; @@ -54,6 +76,28 @@ void createRolesAndPermissions() { regular = createADefaultMemberProfile(); admin = createAThirdDefaultMemberProfile(); assignAdminRole(admin); + + feedbackTemplate = saveFeedbackTemplate(admin.getId()); + reviewPeriod = createAClosedReviewPeriod( + LocalDate.now().minusDays(30).atStartOfDay(), + LocalDate.now().plusDays(1).atStartOfDay()); + questionOne = saveTemplateQuestion(feedbackTemplate, 1); + questionTwo = saveAnotherTemplateQuestion(feedbackTemplate, 2); + feedbackRequest = saveSampleFeedbackRequest(admin, regular, admin, + feedbackTemplate.getId(), + reviewPeriod); + saveSampleFeedbackAnswer(questionOne.getId(), feedbackRequest.getId()); + saveSampleFeedbackAnswer(questionTwo.getId(), feedbackRequest.getId()); + + Kudos kudos = createApprovedKudos(admin.getId()); + createKudosRecipient(kudos.getId(), regular.getId()); + + employeeHours = new EmployeeHours(regular.getEmployeeId(), + Float.valueOf(1413), Float.valueOf(1371), + Float.valueOf(0), LocalDate.now(), + Float.valueOf(1850), LocalDate.now(), + Float.valueOf(90), Float.valueOf(10)); + saveEmployeeHours(employeeHours); } @Test @@ -73,14 +117,15 @@ void uploadReportDataWithoutPermission() { @Test void getReportData() throws JsonProcessingException { + MemberProfile target = regular; HttpRequest request = postData(admin, ADMIN_ROLE); final String response = client.toBlocking().retrieve(request); assertNotNull(response); request = HttpRequest.GET( String.format("/?memberIds=%s&reviewPeriodId=%s", - regular.getId(), - "12345678-e29c-4cf4-9ea4-6baa09405c57")) + target.getId(), + reviewPeriod.getId().toString())) .basicAuth(admin.getWorkEmail(), ADMIN_ROLE); final String data = client.toBlocking().retrieve(request); ObjectMapper objectMapper = new ObjectMapper(); @@ -102,6 +147,8 @@ void getReportData() throws JsonProcessingException { assertNotNull(first.get("reviews")); assertNotNull(first.get("feedback")); assertNotNull(first.get("hours")); + + validateReportData(first, target); } @Test @@ -109,7 +156,7 @@ void getReportDataWithoutPermission() { final HttpRequest request = HttpRequest.GET( String.format("/?memberIds=%s&reviewPeriodId=%s", regular.getId(), - "12345678-e29c-4cf4-9ea4-6baa09405c57")) + reviewPeriod.getId())) .basicAuth(regular.getWorkEmail(), MEMBER_ROLE); HttpClientResponseException responseException = assertThrows(HttpClientResponseException.class, @@ -130,4 +177,58 @@ HttpRequest postData(MemberProfile user, String role) { .basicAuth(user.getWorkEmail(), role) .contentType(MULTIPART_FORM_DATA); } + + void validateReportData(JsonNode node, MemberProfile user) { + final String memberId = user.getId().toString(); + + // Member Info + assertEquals(memberId, node.get("memberId").asText()); + JsonNode profile = node.get("memberProfile"); + assertEquals(user.getFirstName(), profile.get("firstName").asText()); + assertEquals(user.getLastName(), profile.get("lastName").asText()); + assertEquals(user.getTitle(), profile.get("title").asText()); + + // Kudos + ArrayNode kudos = (ArrayNode)node.get("kudos"); + assertEquals(1, kudos.size()); + assertEquals("Default Kudos", kudos.get(0).get("message").asText()); + + // Compensation History + ArrayNode comp = (ArrayNode)node.get("compensationHistory"); + assertEquals(5, comp.size()); + assertEquals(memberId, comp.get(0).get("memberId").asText()); + assertTrue(comp.get(0).get("amount").asDouble() > 0); + + // Current Information + JsonNode curr = node.get("currentInformation"); + assertEquals(memberId, curr.get("memberId").asText()); + assertTrue(curr.get("salary").asDouble() > 0); + assertEquals("$90000 - $150000", curr.get("range").asText()); + assertEquals("$89000 - $155000", curr.get("nationalRange").asText()); + + // Position History + ArrayNode pos = (ArrayNode)node.get("positionHistory"); + assertEquals(3, pos.size()); + assertEquals(memberId, pos.get(2).get("memberId").asText()); + assertEquals("Software Engineer", pos.get(2).get("title").asText()); + + // Feedback + ArrayNode feedback = (ArrayNode)node.get("feedback"); + assertEquals(1, feedback.size()); + ArrayNode answers = (ArrayNode)feedback.get(0).get("answers"); + assertEquals(2, answers.size()); + assertEquals("TEXT", answers.get(0).get("type").asText()); + assertEquals(1, answers.get(0).get("number").asInt()); + + // Hours + JsonNode hours = node.get("hours"); + assertTrue(employeeHours.getContributionHours() == + hours.get("contributionHours").asDouble()); + assertTrue(employeeHours.getPtoHours() == + hours.get("ptoHours").asDouble()); + assertTrue(employeeHours.getOvertimeWorked() == + hours.get("overtimeHours").asDouble()); + assertTrue(employeeHours.getBillableUtilization() == + hours.get("billableUtilization").asDouble()); + } } diff --git a/server/src/test/java/com/objectcomputing/checkins/services/reports/data/currentInformation.csv b/server/src/test/java/com/objectcomputing/checkins/services/reports/data/currentInformation.csv index 32e882221..3ed88d768 100644 --- a/server/src/test/java/com/objectcomputing/checkins/services/reports/data/currentInformation.csv +++ b/server/src/test/java/com/objectcomputing/checkins/services/reports/data/currentInformation.csv @@ -1,2 +1,2 @@ "emailAddress","salary","range","nationalRange", "biography","commitments" -"billm@objectcomputing.com",122222,"$90000 – $150000","$89000 - $155000","Lives in St. Louis","" +"billm@objectcomputing.com",122222,"$90000 - $150000","$89000 - $155000","Lives in St. Louis","" From 06f91fee647fdb4597e70e0c325967ee381752ec Mon Sep 17 00:00:00 2001 From: Tim Yates Date: Tue, 17 Sep 2024 16:00:47 +0100 Subject: [PATCH 51/55] Remove unused imports --- .../checkins/services/reports/CSVProcessor.java | 1 - .../checkins/services/reports/CurrentInformation.java | 1 - .../services/reports/ReportDataController.java | 1 - .../checkins/services/reports/ReportDataDTO.java | 3 --- .../services/reports/ReportDataServicesImpl.java | 2 -- .../services/reports/ReportDataControllerTest.java | 11 ----------- 6 files changed, 19 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/CSVProcessor.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/CSVProcessor.java index 182926eba..c9b590c67 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/CSVProcessor.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/CSVProcessor.java @@ -11,7 +11,6 @@ import java.io.InputStreamReader; import java.io.IOException; import java.time.LocalDate; -import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeParseException; import java.time.temporal.ChronoField; diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java index 2f9aff6c0..2c7b1eee2 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java @@ -11,7 +11,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.UUID; diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java index 49b14fb23..d616748ce 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataController.java @@ -11,7 +11,6 @@ import com.objectcomputing.checkins.services.feedback_answer.FeedbackAnswerServices; import com.objectcomputing.checkins.services.feedback_template.template_question.TemplateQuestionServices; import com.objectcomputing.checkins.services.employee_hours.EmployeeHoursServices; -import com.objectcomputing.checkins.exceptions.NotFoundException; import io.micronaut.http.MediaType; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Post; diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java index e495e167b..2d290145e 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataDTO.java @@ -2,15 +2,12 @@ import com.objectcomputing.checkins.services.memberprofile.MemberProfile; import io.micronaut.core.annotation.Introspected; -import io.micronaut.core.annotation.Nullable; -import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import java.time.LocalDate; -import java.util.Collections; import java.util.List; import java.util.UUID; diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java index fa0cd0566..a56057b63 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/ReportDataServicesImpl.java @@ -3,9 +3,7 @@ import com.objectcomputing.checkins.exceptions.BadArgException; import com.objectcomputing.checkins.exceptions.NotFoundException; import com.objectcomputing.checkins.services.memberprofile.MemberProfile; -import com.objectcomputing.checkins.services.memberprofile.MemberProfileRepository; import com.objectcomputing.checkins.services.memberprofile.currentuser.CurrentUserServices; -import com.objectcomputing.checkins.services.memberprofile.memberphoto.MemberPhotoServiceImpl; import io.micronaut.http.multipart.CompletedFileUpload; import jakarta.inject.Singleton; import org.slf4j.Logger; diff --git a/server/src/test/java/com/objectcomputing/checkins/services/reports/ReportDataControllerTest.java b/server/src/test/java/com/objectcomputing/checkins/services/reports/ReportDataControllerTest.java index dd52e793b..5bd052d2e 100644 --- a/server/src/test/java/com/objectcomputing/checkins/services/reports/ReportDataControllerTest.java +++ b/server/src/test/java/com/objectcomputing/checkins/services/reports/ReportDataControllerTest.java @@ -20,14 +20,9 @@ import com.objectcomputing.checkins.services.feedback_template.FeedbackTemplate; import com.objectcomputing.checkins.services.feedback_request.FeedbackRequest; import com.objectcomputing.checkins.services.feedback_template.template_question.TemplateQuestion; -import com.objectcomputing.checkins.services.feedback_answer.FeedbackAnswer; import com.objectcomputing.checkins.services.employee_hours.EmployeeHours; -import io.micronaut.core.type.Argument; import io.micronaut.http.HttpRequest; -import io.micronaut.http.HttpResponse; -import io.micronaut.http.HttpStatus; -import io.micronaut.http.MutableHttpRequest; import io.micronaut.http.client.HttpClient; import io.micronaut.http.client.annotation.Client; import io.micronaut.http.client.exceptions.HttpClientResponseException; @@ -38,12 +33,6 @@ import org.junit.jupiter.api.Test; import java.io.File; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; import java.time.LocalDate; import static com.objectcomputing.checkins.services.role.RoleType.Constants.ADMIN_ROLE; From c69cda5565e34c3bb9731323f39020f79c134529 Mon Sep 17 00:00:00 2001 From: Tim Yates Date: Tue, 17 Sep 2024 16:09:33 +0100 Subject: [PATCH 52/55] Update CompensationHistory and PositionHistory as we did with CurrentInformation --- .../services/reports/CompensationHistory.java | 20 ++++++++----------- .../services/reports/CurrentInformation.java | 2 +- .../services/reports/PositionHistory.java | 20 ++++++++----------- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java index 1aa337490..b64dbb955 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/CompensationHistory.java @@ -22,19 +22,15 @@ public class CompensationHistory extends CSVProcessor { - @AllArgsConstructor - @Getter - public class Compensation { - private UUID memberId; - private LocalDate startDate; - private float amount; + public record Compensation( + UUID memberId, + LocalDate startDate, + float amount + ) { } private static final Logger LOG = LoggerFactory.getLogger(CompensationHistory.class); - private List history = new ArrayList(); - - public CompensationHistory() { - } + private final List history = new ArrayList<>(); @Override protected void loadImpl(MemberProfileRepository memberProfileRepository, @@ -68,7 +64,7 @@ protected void loadImpl(MemberProfileRepository memberProfileRepository, public List getHistory(UUID memberId) { return history.stream() - .filter(entry -> entry.getMemberId().equals(memberId)) - .collect(Collectors.toList()); + .filter(entry -> entry.memberId().equals(memberId)) + .toList(); } } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java index 2c7b1eee2..6674a576f 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/CurrentInformation.java @@ -29,7 +29,7 @@ public record Information( } private static final Logger LOG = LoggerFactory.getLogger(CurrentInformation.class); - private List information = new ArrayList<>(); + private final List information = new ArrayList<>(); @Override protected void loadImpl(MemberProfileRepository memberProfileRepository, diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/PositionHistory.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/PositionHistory.java index 864e7603e..b8cbe28c5 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/PositionHistory.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/PositionHistory.java @@ -22,19 +22,15 @@ public class PositionHistory extends CSVProcessor { - @AllArgsConstructor - @Getter - public class Position { - private UUID memberId; - private LocalDate date; - private String title; + public record Position( + UUID memberId, + LocalDate date, + String title + ) { } private static final Logger LOG = LoggerFactory.getLogger(PositionHistory.class); - private List history = new ArrayList(); - - public PositionHistory() { - } + private final List history = new ArrayList<>(); @Override protected void loadImpl(MemberProfileRepository memberProfileRepository, @@ -67,7 +63,7 @@ protected void loadImpl(MemberProfileRepository memberProfileRepository, public List getHistory(UUID memberId) { return history.stream() - .filter(entry -> entry.getMemberId().equals(memberId)) - .collect(Collectors.toList()); + .filter(entry -> entry.memberId().equals(memberId)) + .toList(); } } From cac00d7d9a278be852b4a5ebccd950612304ff93 Mon Sep 17 00:00:00 2001 From: Tim Yates Date: Tue, 17 Sep 2024 16:11:36 +0100 Subject: [PATCH 53/55] Use specific assertion --- .../reports/ReportDataControllerTest.java | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/server/src/test/java/com/objectcomputing/checkins/services/reports/ReportDataControllerTest.java b/server/src/test/java/com/objectcomputing/checkins/services/reports/ReportDataControllerTest.java index 5bd052d2e..0dfc46bf3 100644 --- a/server/src/test/java/com/objectcomputing/checkins/services/reports/ReportDataControllerTest.java +++ b/server/src/test/java/com/objectcomputing/checkins/services/reports/ReportDataControllerTest.java @@ -39,6 +39,7 @@ import static com.objectcomputing.checkins.services.role.RoleType.Constants.MEMBER_ROLE; import static io.micronaut.http.MediaType.MULTIPART_FORM_DATA; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -122,8 +123,8 @@ void getReportData() throws JsonProcessingException { // Perform minimal validation of returned data JsonNode root = objectMapper.readTree(data); - assertEquals(root.isArray(), true); - assertEquals(root.isEmpty(), false); + assertTrue(root.isArray()); + assertFalse(root.isEmpty()); ArrayNode arrayNode = (ArrayNode)root; JsonNode first = arrayNode.get(0); @@ -211,13 +212,9 @@ void validateReportData(JsonNode node, MemberProfile user) { // Hours JsonNode hours = node.get("hours"); - assertTrue(employeeHours.getContributionHours() == - hours.get("contributionHours").asDouble()); - assertTrue(employeeHours.getPtoHours() == - hours.get("ptoHours").asDouble()); - assertTrue(employeeHours.getOvertimeWorked() == - hours.get("overtimeHours").asDouble()); - assertTrue(employeeHours.getBillableUtilization() == - hours.get("billableUtilization").asDouble()); + assertEquals(employeeHours.getContributionHours(), hours.get("contributionHours").asDouble(), 0.0); + assertEquals(employeeHours.getPtoHours(), hours.get("ptoHours").asDouble(), 0.0); + assertEquals(employeeHours.getOvertimeWorked(), hours.get("overtimeHours").asDouble(), 0.0); + assertEquals(employeeHours.getBillableUtilization(), hours.get("billableUtilization").asDouble(), 0.0); } } From 4fe2a0327e4f74f762457025667101557871c2b7 Mon Sep 17 00:00:00 2001 From: Tim Yates Date: Tue, 17 Sep 2024 16:20:58 +0100 Subject: [PATCH 54/55] CSVProcessor is an abstract class --- .../checkins/services/reports/CSVProcessor.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/com/objectcomputing/checkins/services/reports/CSVProcessor.java b/server/src/main/java/com/objectcomputing/checkins/services/reports/CSVProcessor.java index c9b590c67..b75cd91d3 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/reports/CSVProcessor.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/reports/CSVProcessor.java @@ -16,10 +16,7 @@ import java.time.temporal.ChronoField; import java.util.List; -public class CSVProcessor { - - protected CSVProcessor() { - } +abstract class CSVProcessor { public void load(MemberProfileRepository memberProfileRepository, ByteBuffer dataSource) throws IOException, @@ -36,8 +33,7 @@ public void load(MemberProfileRepository memberProfileRepository, loadImpl(memberProfileRepository, csvParser); } - protected void loadImpl(MemberProfileRepository memberProfileRepository, - CSVParser csvParser) throws BadArgException {} + protected abstract void loadImpl(MemberProfileRepository memberProfileRepository, CSVParser csvParser) throws BadArgException; protected LocalDate parseDate(String date) { List formatStrings = List.of("yyyy", "M/d/yyyy"); From 20fe29a2812ba84848a614508b7457d2df687bb8 Mon Sep 17 00:00:00 2001 From: Chad Elliott Date: Wed, 18 Sep 2024 09:39:58 -0500 Subject: [PATCH 55/55] Removed duplicate review period. --- .../resources/db/dev/R__Load_testing_data.sql | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/server/src/main/resources/db/dev/R__Load_testing_data.sql b/server/src/main/resources/db/dev/R__Load_testing_data.sql index 6e43f33b2..cfe0d8235 100644 --- a/server/src/main/resources/db/dev/R__Load_testing_data.sql +++ b/server/src/main/resources/db/dev/R__Load_testing_data.sql @@ -1276,7 +1276,12 @@ VALUES INSERT INTO review_periods (id, name, review_status, review_template_id, self_review_template_id, launch_date, self_review_close_date, close_date, period_start_date, period_end_date) VALUES - ('12345678-e29c-4cf4-9ea4-6baa09405c57', 'Review Period 1', 'CLOSED', 'd1e94b60-47c4-4945-87d1-4dc88f088e57', '926a37a4-4ded-4633-8900-715b0383aecc', '2024-09-01', '2024-09-02', '2024-09-03', '2024-01-01', '2024-12-31'); + ('12345678-e29c-4cf4-9ea4-6baa09405c57', 'Review Period 1', 'OPEN', 'd1e94b60-47c4-4945-87d1-4dc88f088e57', '926a37a4-4ded-4633-8900-715b0383aecc', '2024-09-01', '2024-09-02', '2024-09-03', '2024-01-01', '2024-12-31'); + +INSERT INTO review_periods +(id, name, review_status, review_template_id, self_review_template_id, launch_date, self_review_close_date, close_date, period_start_date, period_end_date) +VALUES + ('12345678-e29c-4cf4-9ea4-6baa09405c58', 'Review Period 2', 'PLANNING', 'd1e94b60-47c4-4945-87d1-4dc88f088e57', '926a37a4-4ded-4633-8900-715b0383aecc', '2024-09-10', '2024-09-11', '2024-09-12', '2024-01-01', '2024-12-31'); -- CAE - Self-Review feedback request, Creator: Big Boss INSERT INTO feedback_requests @@ -1940,14 +1945,3 @@ INSERT INTO role_documentation (role_id, document_id, display_order) VALUES ('d03f5f0b-e29c-4cf4-9ea4-6baa09405c56', 'b553d4c0-9b7a-4691-8fe0-e3bdda4f67ae', 3); - --- CAE - Review Periods -INSERT INTO review_periods -(id, name, review_status, review_template_id, self_review_template_id, launch_date, self_review_close_date, close_date, period_start_date, period_end_date) -VALUES - ('12345678-e29c-4cf4-9ea4-6baa09405c57', 'Review Period 1', 'CLOSED', 'd1e94b60-47c4-4945-87d1-4dc88f088e57', '926a37a4-4ded-4633-8900-715b0383aecc', '2024-09-01', '2024-09-02', '2024-09-03', '2024-01-01', '2024-12-31'); - -INSERT INTO review_periods -(id, name, review_status, review_template_id, self_review_template_id, launch_date, self_review_close_date, close_date, period_start_date, period_end_date) -VALUES - ('12345678-e29c-4cf4-9ea4-6baa09405c58', 'Review Period 2', 'PLANNING', 'd1e94b60-47c4-4945-87d1-4dc88f088e57', '926a37a4-4ded-4633-8900-715b0383aecc', '2024-09-10', '2024-09-11', '2024-09-12', '2024-01-01', '2024-12-31');