Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: create favourite openings api #266

Merged
merged 22 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions backend/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@
<version>1.18.30</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.ocpsoft.prettytime</groupId>
<artifactId>prettytime</artifactId>
<version>5.0.7.Final</version>
</dependency>

<!-- DevOps -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package ca.bc.gov.restapi.results.common.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.server.ResponseStatusException;

/** This class represents a generic not found request. */
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class NotFoundGenericException extends ResponseStatusException {

public NotFoundGenericException(String entityName) {
super(HttpStatus.NOT_FOUND, String.format("%s record(s) not found!", entityName));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package ca.bc.gov.restapi.results.common.exception;

/** This class represents an error, when a Opening was not found. */
public class OpeningNotFoundException extends NotFoundGenericException {

public OpeningNotFoundException() {
super("UserOpening");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package ca.bc.gov.restapi.results.common.exception;

/** This class represents a UserOpeningEntity not found in the database. */
public class UserOpeningNotFoundException extends NotFoundGenericException {

public UserOpeningNotFoundException() {
super("UserOpeningEntity");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package ca.bc.gov.restapi.results.common.util;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.format.DateTimeFormatter;
import java.util.Objects;

/** This class contains useful methods for parsing and handling timestamps. */
public class TimestampUtil {

/**
* Parses a date string to a {@link LocalDateTime} instance. Format: yyyy-MM-dd.
*
* @param dateStr The date to be parsed
* @return LocalDateTime parsed or null if a null value is found.
*/
public static LocalDateTime parseDateString(String dateStr) {
if (Objects.isNull(dateStr)) {
return null;
}

LocalDate entryDateStartLd =
LocalDate.parse(dateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
return entryDateStartLd.atStartOfDay();
}

/**
* Extract the number based on the difference between today and the day the Opening got created.
*
* @param entryLocalDateTime The LocalDateTime representing the opening creation timestamp.
* @return An integer representing the index
*/
public static int getLocalDateTimeIndex(LocalDateTime entryLocalDateTime) {
// index 0 -> 0 to 5 months
// index 1 -> 6 to 11 months
// index 2 -> 12 to 17 months
// index 3 -> 18+ months
LocalDate entryLocalDate = entryLocalDateTime.toLocalDate();
LocalDate now = LocalDate.now();

Period diff = Period.between(entryLocalDate, now);
int totalMonths = diff.getMonths() + (diff.getYears() * 12);
if (totalMonths <= 5) {
return 0;
} else if (totalMonths <= 11) {
return 1;
} else if (totalMonths <= 17) {
return 2;
} else {
return 3;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public LocalContainerEntityManagerFactoryBean postgresEntityManagerFactory(
Properties jpaProps = new Properties();
jpaProps.setProperty("hibernate.default_schema", "silva");
jpaProps.setProperty("hibernate.ddl-auto", "update");
jpaProps.setProperty("defer-datasource-initialization", "true");
jpaProps.setProperty("jpa.defer-datasource-initialization", "true");
jpaProps.setProperty("sql.init.mode", "always");

LocalContainerEntityManagerFactoryBean build =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@
import java.time.LocalDateTime;

/** This record contains all possible filters for the dashboard openings per years api. */
public record OpeningsPerYearFiltersDto(
String orgUnit, String status, LocalDateTime entryDateStart, LocalDateTime entryDateEnd) {}
public record DashboardFiltesDto(
String orgUnit,
String status,
LocalDateTime entryDateStart,
LocalDateTime entryDateEnd,
String clientNumber) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ca.bc.gov.restapi.results.postgres.dto;

import java.math.BigDecimal;

/** This record represent a slice of the free growing milestone chart. */
public record FreeGrowingMilestonesDto(
Integer index, String label, Integer amount, BigDecimal percentage) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package ca.bc.gov.restapi.results.postgres.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime;

/** This record represents an opening activity in the requests tab. */
public record MyRecentActionsRequestsDto(
@Schema(description = "Full description of the action made by the user.", example = "Update")
String activityType,
@Schema(
description = "System generated value uniquely identifying the opening.",
example = "1541297")
Long openingId,
@Schema(
description =
"""
A code indicating the status of the prescription. Examples include but are not
limited to DFT (draft) and APP (approved). A subset of the STATUS_CODE table.
""",
example = "APP")
String statusCode,
@Schema(
description =
"""
The code description indicating the status of the prescription. Examples include but
are not limited to Draft (DFT) and Approved (APP). A subset of the STATUS_CODE table.
""",
example = "Approved")
String statusDescription,
@Schema(
description = "The date and time of the activity action in for 'time ago' format",
example = "1 minute ago")
String lastUpdatedLabel,
@Schema(description = "The date and time of the activity action") LocalDateTime lastUpdated) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package ca.bc.gov.restapi.results.postgres.dto;

import io.swagger.v3.oas.annotations.media.Schema;

/** This record represents the JSON body request for saving Openings as favourite. */
public record UserOpeningCreateDto(
@Schema(
description = "System generated value uniquely identifying the opening.",
example = "1541297")
String openingId) {}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package ca.bc.gov.restapi.results.postgres.endpoint;

import ca.bc.gov.restapi.results.common.util.TimestampUtil;
import ca.bc.gov.restapi.results.postgres.dto.DashboardFiltesDto;
import ca.bc.gov.restapi.results.postgres.dto.FreeGrowingMilestonesDto;
import ca.bc.gov.restapi.results.postgres.dto.MyRecentActionsRequestsDto;
import ca.bc.gov.restapi.results.postgres.dto.OpeningsPerYearDto;
import ca.bc.gov.restapi.results.postgres.dto.OpeningsPerYearFiltersDto;
import ca.bc.gov.restapi.results.postgres.service.DashboardMetricsService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
Expand All @@ -10,11 +13,7 @@
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -25,32 +24,36 @@
/** This class holds resources for the dashboard metrics page. */
@RestController
@RequestMapping("/api/dashboard-metrics")
@Tag(name = "Dashboard Metrics", description = "Resources fot the Dashboard metrics charts")
@Tag(
name = "Dashboard Metrics (SILVA)",
description = "Endpoints fot the Dashboard metrics charts in the `SILVA` schema")
@RequiredArgsConstructor
public class DashboardMetricsEndpoint {

private final DashboardMetricsService dashboardMetricsService;

/**
* Get data for the Submission Trends Chart, Openings per Year.
* Gets data for the Opening submission trends Chart (Openings per year) on the Dashboard SILVA
* page.
*
* @param orgUnitCode The district code to filter.
* @param statusCode The opening status code to filter.
* @param entryDateStart The opening entry timestamp start date filter.
* @param entryDateEnd The opening entry timestamp end date filter.
* @param orgUnitCode Optional district code filter.
* @param statusCode Optional opening status code filter.
* @param entryDateStart Optional opening entry timestamp start date filter.
* @param entryDateEnd Optional opening entry timestamp end date filter.
* @return A list of values to populate the chart or 204 no content if no data.
*/
@GetMapping("/submission-trends")
@Operation(
summary = "Get data for the Submission Trends Chart, Openings per Year",
description = "Fetches data from the last years for the openings per year chart.",
summary = "Gets data for the Opening submission trends Chart (Openings per year).",
description = "Fetches data from the last twelve months for the openings per year chart.",
responses = {
@ApiResponse(
responseCode = "200",
description = "An array with twelve objects for the last 12 months."),
description = "An array with twelve objects for the last 12 months.",
content = @Content(mediaType = "application/json")),
@ApiResponse(
responseCode = "204",
description = "No data found in the dable. No response body."),
description = "No data found on the table. No response body."),
@ApiResponse(
responseCode = "401",
description = "Access token is missing or invalid",
Expand Down Expand Up @@ -89,31 +92,130 @@ public ResponseEntity<List<OpeningsPerYearDto>> getOpeningsSubmissionTrends(
required = false,
example = "2024-03-11")
String entryDateEnd) {
LocalDateTime entryDateStartDate = null;
LocalDateTime entryDateEndDate = null;
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
DashboardFiltesDto filtersDto =
new DashboardFiltesDto(
orgUnitCode,
statusCode,
TimestampUtil.parseDateString(entryDateStart),
TimestampUtil.parseDateString(entryDateEnd),
null);

if (!Objects.isNull(entryDateStart)) {
LocalDate entryDateStartLd = LocalDate.parse(entryDateStart, fmt);
entryDateStartDate = entryDateStartLd.atStartOfDay();
List<OpeningsPerYearDto> resultList =
dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto);

if (resultList.isEmpty()) {
return ResponseEntity.noContent().build();
}

if (!Objects.isNull(entryDateEnd)) {
LocalDate entryDateEndLd = LocalDate.parse(entryDateEnd, fmt);
entryDateEndDate = entryDateEndLd.atStartOfDay();
return ResponseEntity.ok(resultList);
}

/**
* Gets data for the Free growing Chart on the Dashboard SILVA page.
*
* @param orgUnitCode Optional district code filter.
* @param clientNumber Optional client number filter.
* @param entryDateStart Optional opening entry timestamp start date filter.
* @param entryDateEnd Optional opening entry timestamp end date filter.
* @return A list of values to populate the chart or 204 no content if no data.
*/
@GetMapping("/free-growing-milestones")
@Operation(
summary = "Gets data for the Free growing Chart on the Dashboard SILVA page.",
description = "Fetches data from the last twelve months for the Free growing chart.",
responses = {
@ApiResponse(
responseCode = "200",
description = "An array with four objects, one for each piece of the chart.",
content = @Content(mediaType = "application/json")),
@ApiResponse(
responseCode = "204",
description = "No data found on the table. No response body."),
@ApiResponse(
responseCode = "401",
description = "Access token is missing or invalid",
content = @Content(schema = @Schema(implementation = Void.class)))
})
public ResponseEntity<List<FreeGrowingMilestonesDto>> getFreeGrowingMilestonesData(
@RequestParam(value = "orgUnitCode", required = false)
@Parameter(
name = "orgUnitCode",
in = ParameterIn.QUERY,
description = "The Org Unit code to filter, same as District",
required = false,
example = "DCR")
String orgUnitCode,
@RequestParam(value = "clientNumber", required = false)
@Parameter(
name = "clientNumber",
in = ParameterIn.QUERY,
description = "The Client Number to filter",
required = false,
example = "00012797")
String clientNumber,
@RequestParam(value = "entryDateStart", required = false)
@Parameter(
name = "entryDateStart",
in = ParameterIn.QUERY,
description = "The Openins entry timestamp start date to filter, format yyyy-MM-dd",
required = false,
example = "2024-03-11")
String entryDateStart,
@RequestParam(value = "entryDateEnd", required = false)
@Parameter(
name = "entryDateEnd",
in = ParameterIn.QUERY,
description = "The Openins entry timestamp end date to filter, format yyyy-MM-dd",
required = false,
example = "2024-03-11")
String entryDateEnd) {
DashboardFiltesDto filtersDto =
new DashboardFiltesDto(
orgUnitCode,
null,
TimestampUtil.parseDateString(entryDateStart),
TimestampUtil.parseDateString(entryDateEnd),
clientNumber);
List<FreeGrowingMilestonesDto> milestonesDto =
dashboardMetricsService.getFreeGrowingMilestoneChartData(filtersDto);

if (milestonesDto.isEmpty()) {
return ResponseEntity.noContent().build();
}

OpeningsPerYearFiltersDto filtersDto =
new OpeningsPerYearFiltersDto(
orgUnitCode, statusCode, entryDateStartDate, entryDateEndDate);
return ResponseEntity.ok(milestonesDto);
}

List<OpeningsPerYearDto> resultList =
dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto);
/**
* Gets the last 5 most recent updated openings for the request user.
*
* @return A list of values to populate the chart or 204 no content if no data.
*/
@GetMapping("/my-recent-actions/requests")
@Operation(
summary = "Gets the last 5 most recent updated openings for the request user.",
description = "Fetches data for the My recent actions table, Requests tab",
responses = {
@ApiResponse(
responseCode = "200",
description = "An array with five objects, one for opening row.",
content = @Content(mediaType = "application/json")),
@ApiResponse(
responseCode = "204",
description = "No data found for the user. No response body."),
@ApiResponse(
responseCode = "401",
description = "Access token is missing or invalid",
content = @Content(schema = @Schema(implementation = Void.class)))
})
public ResponseEntity<List<MyRecentActionsRequestsDto>> getUserRecentOpeningsActions() {
List<MyRecentActionsRequestsDto> actionsDto =
dashboardMetricsService.getUserRecentOpeningsActions();

if (resultList.isEmpty()) {
if (actionsDto.isEmpty()) {
return ResponseEntity.noContent().build();
}

return ResponseEntity.ok(resultList);
return ResponseEntity.ok(actionsDto);
}
}
Loading
Loading