Skip to content

Commit

Permalink
Merge pull request #2586 from objectcomputing/feature-2571/merit-eval…
Browse files Browse the repository at this point in the history
…uation-report

Feature 2571/merit evaluation report
  • Loading branch information
mkimberlin authored Sep 18, 2024
2 parents 661bff2 + 20fe29a commit c7bf92f
Show file tree
Hide file tree
Showing 34 changed files with 2,621 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ public FileInfoDTO upload(@NotNull UUID checkInId, CompletedFileUpload file) {
return fileServices.uploadFile(checkInId, file);
}

@Post(consumes = MediaType.MULTIPART_FORM_DATA)
@Status(HttpStatus.CREATED)
public FileInfoDTO uploadDocument(String directory, String name, String text) {
return fileServices.uploadDocument(directory, name, text);
}

/**
* Delete a document from Google Drive
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ public interface FileServices {
Set<FileInfoDTO> findFiles(UUID checkInId);
File downloadFiles(String uploadDocId);
FileInfoDTO uploadFile(UUID checkInID, CompletedFileUpload file);
FileInfoDTO uploadDocument(String directory, String name, String text);
boolean deleteFile(String uploadDocId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.charset.StandardCharsets;
import java.io.InputStream;
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collections;
Expand Down Expand Up @@ -209,6 +212,101 @@ 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();
boolean isAdmin = currentUserServices.isAdmin();
validate(!isAdmin, "You are not authorized to perform this operation");

try {
Drive drive = googleApiAccess.getDrive();
validate(drive == null, "Unable to access Google Drive");

String rootDirId = googleServiceConfiguration.getDirectoryId();
validate(rootDirId == null, "No destination folder has been configured. Contact your administrator for assistance.");

// Check if folder already exists on google drive. If exists, return folderId and name
FileList driveIndex = getFoldersInRoot(drive, rootDirId);
File folderOnDrive = driveIndex.getFiles().stream()
.filter(s -> directoryName.equalsIgnoreCase(s.getName()))
.findFirst()
.orElse(null);

// If folder does not exist on Drive, create a new folder in the format name-date
if (folderOnDrive == null) {
folderOnDrive = createNewDirectoryOnDrive(drive, directoryName, rootDirId);
}

// Set the file metadata
File fileMetadata = new File();
fileMetadata.setName(name);
fileMetadata.setMimeType(MediaType.TEXT_MARKDOWN_TYPE.toString());
fileMetadata.setParents(Collections.singletonList(folderOnDrive.getId()));

// Upload file to google drive
InputStream is = new ByteArrayInputStream(
StandardCharsets.UTF_8.encode(text).array());
InputStreamContent content = new InputStreamContent(
MediaType.TEXT_MARKDOWN_TYPE.toString(), is);
File uploadedFile = drive.files().create(fileMetadata, content)
.setSupportsAllDrives(true)
.setFields("id, size, name")
.execute();

// 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 = '%s' and trashed != true", folderOnDrive.getId(), GOOGLE_DOC_TYPE))
.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) {
LOG.error("Error while trashing " + file.getName(), e);
} catch (IOException e) {
LOG.error("Error while trashing " + file.getName(), e);
}
}
}

// Copy the file to a Google doc
File docFile = new File();
docFile.setName(name);
docFile.setMimeType(GOOGLE_DOC_TYPE);
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());
} catch (IOException e) {
LOG.error("Unexpected error processing file upload.", e);
throw new FileRetrievalException(e.getMessage());
}
}

private FileList getFoldersInRoot(Drive drive, String rootDirId) throws IOException {
return drive.files().list().setSupportsAllDrives(true)
.setIncludeItemsFromAllDrives(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
public interface QuestionServices {
Question saveQuestion(Question question);
Set<Question> readAllQuestions();
Question findById(UUID skillId);
Question findById(UUID id);
Question update(Question question);
Set<Question> findByText(String text);
Set<Question> findByCategoryId(UUID categoryId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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.DateTimeFormatterBuilder;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
import java.util.List;

abstract class 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 abstract void loadImpl(MemberProfileRepository memberProfileRepository, CSVParser csvParser) throws BadArgException;

protected LocalDate parseDate(String date) {
List<String> 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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.objectcomputing.checkins.services.reports;

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.CSVParser;
import org.apache.commons.csv.CSVRecord;

import lombok.AllArgsConstructor;
import lombok.Getter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.Optional;
import java.util.stream.Collectors;

public class CompensationHistory extends CSVProcessor {

public record Compensation(
UUID memberId,
LocalDate startDate,
float amount
) {
}

private static final Logger LOG = LoggerFactory.getLogger(CompensationHistory.class);
private final List<Compensation> history = new ArrayList<>();

@Override
protected void loadImpl(MemberProfileRepository memberProfileRepository,
CSVParser csvParser) throws BadArgException {
history.clear();
for (CSVRecord csvRecord : csvParser) {
try {
String emailAddress = csvRecord.get("emailAddress");
Optional<MemberProfile> 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);
}
} catch(IllegalArgumentException ex) {
throw new BadArgException("Unable to parse the compensation history");
}
}
}

public List<Compensation> getHistory(UUID memberId) {
return history.stream()
.filter(entry -> entry.memberId().equals(memberId))
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
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;
import com.objectcomputing.checkins.exceptions.BadArgException;

import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

public class CurrentInformation extends CSVProcessor {

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 final List<Information> information = new ArrayList<>();

@Override
protected void loadImpl(MemberProfileRepository memberProfileRepository,
CSVParser csvParser) throws BadArgException {
information.clear();
for (CSVRecord csvRecord : csvParser) {
try {
String emailAddress = csvRecord.get("emailAddress");
Optional<MemberProfile> 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.
return information.stream()
.filter(entry -> entry.memberId().equals(memberId))
.findFirst()
.orElseThrow(() -> new NotFoundException("Current Information not found for member: " + memberId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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 final String type;
private final int number;
}

private String name;
private List<Answer> answers;
}

Loading

0 comments on commit c7bf92f

Please sign in to comment.