Skip to content

Commit

Permalink
Workload snapshots (#387)
Browse files Browse the repository at this point in the history
* adding workload snapshots

* tie snapshots to budget scenarios

* switch to using workload service

* add ability to download snapshot

* show instructor note on every row

* update migration version

* fix historical download

* merge fixes

* cleanup

* add snapshot name to download

* new lines

* order by instructor type and name

* building report table sheet

* update instructor row values

* building summary table

* show unassigned courses

* add unassigned totals to table

* minor code cleanup

* delete comments

* remove unused variable

* add instructor subtotal row

* enforce instructor type display order

* sort by term

* remove extra sorting

* formatting

* fix last offered enrollment

* fix associate instructors listings

* remove extra sort

* skip instructor subtotal if no assignments

* refactor for readability

* more refactoring

* use enum

* match number types

* extract duplicate code

* reduce method param

* fill in summary totals

* update overloaded param

* rename var

* fix summary table counts

* handle variable units

* use termCode on sectionGroup if available

* match existing download display

* handle variable unit courses

* increment migration version

* delete old migration file

* add student credit hours to unassigned assignments

* Add snapshot name to raw data

* add ability to download multiple departments

* include previous years for comparison

* clean up

* INT width is deprecated

* cleanup

* handle placeholder courses (1xx/2xx)
  • Loading branch information
jaroldwong authored Jan 25, 2024
1 parent 80b2b22 commit 6734c7f
Show file tree
Hide file tree
Showing 22 changed files with 1,660 additions and 476 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.View;
Expand Down Expand Up @@ -47,6 +48,7 @@ public class BudgetViewController {
@Inject InstructorTypeService instructorTypeService;
@Inject ExpenseItemService expenseItemService;
@Inject ExpenseItemTypeService expenseItemTypeService;
@Inject WorkloadSnapshotService workloadSnapshotService;

@Value("${IPA_URL_API}")
String ipaUrlApi;
Expand Down Expand Up @@ -122,6 +124,7 @@ public BudgetScenarioView createBudgetScenario(@PathVariable long budgetId,

@RequestMapping(value = "/api/budgetView/budgets/{budgetId}/budgetScenarios/{budgetScenarioId}/budgetRequest", method = RequestMethod.POST, produces = "application/json")
@ResponseBody
@Transactional
public BudgetScenarioView createBudgetRequestScenario(@PathVariable long budgetId,
@PathVariable long budgetScenarioId,
HttpServletResponse httpResponse) {
Expand All @@ -140,6 +143,8 @@ public BudgetScenarioView createBudgetRequestScenario(@PathVariable long budgetI

BudgetScenario budgetRequestScenario = budgetScenarioService.createBudgetRequestScenario(workGroupId, budgetScenarioId);

WorkloadSnapshot workloadSnapshot = workloadSnapshotService.create(workGroupId, budgetRequestScenario.getId());

return budgetViewFactory.createBudgetScenarioView(budgetRequestScenario);
};

Expand Down Expand Up @@ -181,6 +186,8 @@ public Long deleteBudgetScenario(@PathVariable long budgetScenarioId,
Long workGroupId = budgetScenario.getBudget().getSchedule().getWorkgroup().getId();
authorizer.hasWorkgroupRoles(workGroupId, "academicPlanner", "reviewer");

workloadSnapshotService.deleteByBudgetScenarioId(budgetScenarioId);

budgetScenarioService.deleteById(budgetScenarioId);

return budgetScenarioId;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
package edu.ucdavis.dss.ipa.api.components.workloadSummaryReport;

import com.amazonaws.services.s3.model.ObjectMetadata;
import edu.ucdavis.dss.ipa.api.components.workloadSummaryReport.views.WorkloadSummaryReportView;
import edu.ucdavis.dss.ipa.api.components.workloadSummaryReport.views.factories.WorkloadSummaryReportViewFactory;
import edu.ucdavis.dss.ipa.entities.User;
import edu.ucdavis.dss.ipa.entities.UserRole;
import edu.ucdavis.dss.ipa.entities.Workgroup;
import edu.ucdavis.dss.ipa.entities.WorkloadAssignment;
import edu.ucdavis.dss.ipa.entities.WorkloadSnapshot;
import edu.ucdavis.dss.ipa.security.Authorization;
import edu.ucdavis.dss.ipa.security.Authorizer;
import edu.ucdavis.dss.ipa.security.UrlEncryptor;
import edu.ucdavis.dss.ipa.services.UserService;
import edu.ucdavis.dss.ipa.services.WorkloadAssignmentService;
import edu.ucdavis.dss.ipa.services.WorkloadSnapshotService;
import edu.ucdavis.dss.ipa.utilities.EmailService;
import edu.ucdavis.dss.ipa.utilities.S3Service;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import javax.inject.Inject;
Expand All @@ -29,6 +35,7 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
Expand All @@ -39,6 +46,11 @@
public class WorkloadSummaryReportController {
@Inject
WorkloadSummaryReportViewFactory workloadSummaryReportViewFactory;
@Inject
WorkloadAssignmentService workloadAssignmentService;
@Inject
WorkloadSnapshotService workloadSnapshotService;

@Inject
S3Service s3Service;
@Inject
Expand All @@ -58,11 +70,35 @@ public class WorkloadSummaryReportController {

@RequestMapping(value = "/api/workloadSummaryReport/{workgroupId}/years/{year}", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
public WorkloadSummaryReportView getWorkloadSummaryReportView(
public List<WorkloadAssignment> getWorkloadSummaryReportView(
@PathVariable long workgroupId, @PathVariable long year) {
authorizer.hasWorkgroupRoles(workgroupId, "academicPlanner", "reviewer");

return workloadSummaryReportViewFactory.createWorkloadSummaryReportView(workgroupId, year);
return workloadAssignmentService.generateWorkloadAssignments(workgroupId, year);
}

@RequestMapping(value = "/api/years/{year}/workloadSnapshots", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
public Map<String, Map<String, Object>> getUserWorkgroupsSnapshots(@PathVariable long year,
HttpServletResponse httpResponse) {
User currentUser = userService.getOneByLoginId(authorization.getLoginId());
List<Workgroup> userWorkgroups = currentUser.getWorkgroups();
Map<String, Map<String, Object>> departmentSnapshots = new HashMap<>();

for (Workgroup userWorkgroup : userWorkgroups) {
List<WorkloadSnapshot> workloadSnapshots = new ArrayList<>();
workloadSnapshots.addAll(workloadSnapshotService.findByWorkgroupIdAndYear(userWorkgroup.getId(), year - 1));
workloadSnapshots.addAll(workloadSnapshotService.findByWorkgroupIdAndYear(userWorkgroup.getId(), year));

Map<String, Object> department = new HashMap<>();
department.put("name", userWorkgroup.getName());
department.put("workgroupId", userWorkgroup.getId());
department.put("snapshots", workloadSnapshots);

departmentSnapshots.put(userWorkgroup.getName(), department);
}

return departmentSnapshots;
}

@RequestMapping(value = "/api/workloadSummaryReport/{workgroupIds}/years/{year}/generateExcel", method = RequestMethod.GET)
Expand Down Expand Up @@ -114,11 +150,13 @@ public View downloadExcel(@PathVariable long[] workgroupId, @PathVariable long y
}
}

@RequestMapping(value = "/api/workloadSummaryReport/{workgroupId}/years/{year}/generateMultiple", method = RequestMethod.GET)
@RequestMapping(value = "/api/workloadSummaryReport/{workgroupId}/years/{year}/generateMultiple", method = RequestMethod.POST)
public ResponseEntity generateMultipleDepartments(@PathVariable long workgroupId,
@PathVariable long year) {
@PathVariable long year,
@RequestBody Optional<Map<Long, List<Long>>> departmentSnapshots
) {
authorizer.isDeansOffice();
final String fileName = year + "_Workload_Summary_Report.xlsx";
final String fileName = year + (departmentSnapshots.isPresent() ? "_Workload_Snapshots" : "_Workload_Summary_Report") + ".xlsx";

// overwrite with empty file to update modified time
s3Service.upload(fileName, new byte[0]);
Expand All @@ -127,14 +165,17 @@ public ResponseEntity generateMultipleDepartments(@PathVariable long workgroupId
authorization.getUserRoles().stream().filter(ur -> ur.getRole().getName().equals("academicPlanner")).map(
UserRole::getWorkgroupIdentification).mapToLong(Long::longValue).toArray();


User user = userService.getOneByLoginId(authorization.getRealUserLoginId());
String downloadUrl = ipaUrlFrontend + "/summary/" + workgroupId + "/" + year + "?mode=download";

CompletableFuture.supplyAsync(
() -> {
try {
return workloadSummaryReportViewFactory.createWorkloadSummaryReportBytes(workgroupIds, year);
if (departmentSnapshots.isPresent()) {
return workloadSummaryReportViewFactory.createWorkloadSummaryReportBytes(departmentSnapshots.get(), year);
} else {
return workloadSummaryReportViewFactory.createWorkloadSummaryReportBytes(workgroupIds, year);
}
} catch (Exception e) {
e.printStackTrace();
return null;
Expand Down Expand Up @@ -165,11 +206,18 @@ public ResponseEntity generateMultipleDepartments(@PathVariable long workgroupId
return new ResponseEntity<>(HttpStatus.ACCEPTED);
}

@RequestMapping(value = "/api/workloadSummaryReport/{workgroupId}/years/{year}/downloadMultiple", method = RequestMethod.POST)
public ResponseEntity downloadMultipleDepartments(@PathVariable long workgroupId, @PathVariable long year) {
@RequestMapping(value = "/api/workloadSummaryReport/{workgroupId}/years/{year}/downloadMultiple/{file}", method = RequestMethod.POST)
public ResponseEntity downloadMultipleDepartments(@PathVariable long workgroupId, @PathVariable long year, @PathVariable String file) {
authorizer.isDeansOffice();

byte[] bytes = s3Service.download(year + "_Workload_Summary_Report.xlsx");
String filename = null;
if (file.equals("workloadSummaries")) {
filename = year + "_Workload_Summary_Report.xlsx";
} else if (file.equals("workloadSnapshots")) {
filename = year + "_Workload_Snapshots.xlsx";
}

byte[] bytes = s3Service.download(filename);
ByteArrayResource resource = new ByteArrayResource(bytes);

return ResponseEntity.ok()
Expand Down Expand Up @@ -227,17 +275,30 @@ public View downloadHistoricalExcel(@PathVariable long workgroupId, @PathVariabl
}

@RequestMapping(value = "/api/workloadSummaryReport/years/{year}/download/status", method = RequestMethod.GET, produces = "application/json")
public Map<String, Object> getDownloadStatus(@PathVariable long year) {
public Map<String, Map<String, Long>> getDownloadStatus(@PathVariable long year) {
authorizer.isDeansOffice();

ObjectMetadata metadata = s3Service.getMetadata(year + "_Workload_Summary_Report.xlsx");
if (metadata != null) {
Map<String, Object> md = new HashMap<>();
md.put("lastModified", metadata.getLastModified().getTime());
md.put("contentLength", metadata.getContentLength());
Map<String, Map<String, Long>> status = new HashMap<>();

ObjectMetadata workloadSummaries = s3Service.getMetadata(year + "_Workload_Summary_Report.xlsx");
ObjectMetadata workloadSnapshots = s3Service.getMetadata(year + "_Workload_Snapshots.xlsx");
if (workloadSummaries != null) {
Map<String, Long> md = new HashMap<>();

md.put("lastModified", workloadSummaries.getLastModified().getTime());
md.put("contentLength", workloadSummaries.getContentLength());

return md;
status.put("workloadSummaries", md);
}
return null;

if (workloadSnapshots != null) {
Map<String, Long> md = new HashMap<>();
md.put("lastModified", workloadSnapshots.getLastModified().getTime());
md.put("contentLength", workloadSnapshots.getContentLength());

status.put("workloadSnapshots", md);
}

return status.isEmpty() ? null : status;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package edu.ucdavis.dss.ipa.api.components.workloadSummaryReport.views;

import edu.ucdavis.dss.ipa.entities.WorkloadAssignment;
import edu.ucdavis.dss.ipa.utilities.ExcelHelper;
import java.util.Arrays;
import java.util.Comparator;
Expand All @@ -13,16 +14,16 @@
import org.springframework.web.servlet.view.document.AbstractXlsxView;

public class WorkloadHistoricalReportExcelView extends AbstractXlsxView {
private Map<Long, List<InstructorAssignment>> instructorAssignmentsMap;
private Map<Long, List<WorkloadAssignment>> workloadAssignmentMap;

public WorkloadHistoricalReportExcelView(Map<Long, List<InstructorAssignment>> instructorAssignmentsMap) {
this.instructorAssignmentsMap = instructorAssignmentsMap;
public WorkloadHistoricalReportExcelView(Map<Long, List<WorkloadAssignment>> workloadAssignmentMap) {
this.workloadAssignmentMap = workloadAssignmentMap;
}

@Override
protected void buildExcelDocument(Map<String, Object> model, Workbook workbook,
HttpServletRequest request, HttpServletResponse response) {
List<Long> years = this.instructorAssignmentsMap.keySet().stream().sorted(Comparator.reverseOrder()).collect(
List<Long> years = this.workloadAssignmentMap.keySet().stream().sorted(Comparator.reverseOrder()).collect(
Collectors.toList());

String filename = "attachment; filename=\"" + years.get(0) + " Workload Historical Report.xlsx";
Expand All @@ -37,8 +38,8 @@ protected void buildExcelDocument(Map<String, Object> model, Workbook workbook,
"Offering", "Enrollment", "Planned Seats", "Previous Enrollment (YoY)",
"Previous Enrollment (Last Offered)", "Units", "SCH", "Note"));

for (InstructorAssignment instructorAssignment : instructorAssignmentsMap.get(year)) {
ExcelHelper.writeRowToSheet(worksheet, instructorAssignment.toList());
for (WorkloadAssignment workloadAssignment : workloadAssignmentMap.get(year)) {
ExcelHelper.writeRowToSheet(worksheet, workloadAssignment.toList());
}
}

Expand Down
Loading

0 comments on commit 6734c7f

Please sign in to comment.