From d016b462869f5a2c7993dbcd5c8fa42451059698 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Tue, 26 Mar 2024 17:28:58 -0300 Subject: [PATCH 01/19] feat: openings per year api jira issue silva-331 --- .../postgres/dto/OpeningsPerYearDto.java | 10 ++ .../dto/OpeningsPerYearFiltersDto.java | 6 ++ .../endpoint/DashboardMetricsEndpoint.java | 52 ++++++++++ .../entity/OpeningsLastYearEntity.java | 35 +++++++ .../OpeningsLastYearRepository.java | 6 ++ .../service/DashboardMetricsService.java | 96 +++++++++++++++++++ .../V4__update_metrics_charts_tables.sql | 9 ++ 7 files changed, 214 insertions(+) create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/OpeningsPerYearDto.java create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/OpeningsPerYearFiltersDto.java create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/OpeningsLastYearEntity.java create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepository.java create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsService.java create mode 100644 backend/src/main/resources/db/migration/V4__update_metrics_charts_tables.sql diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/OpeningsPerYearDto.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/OpeningsPerYearDto.java new file mode 100644 index 00000000..359f4902 --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/OpeningsPerYearDto.java @@ -0,0 +1,10 @@ +package ca.bc.gov.restapi.results.postgres.dto; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** This record represents a record for the "Opening submission trends" chart. */ +@Schema( + description = "This record represents a record for the \"Opening submission trends\" chart.") +public record OpeningsPerYearDto( + @Schema(description = "The `x` value with the month name.", example = "Mar") Integer month, + @Schema(description = "The `y` value with the month value.", example = "70") Integer amount) {} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/OpeningsPerYearFiltersDto.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/OpeningsPerYearFiltersDto.java new file mode 100644 index 00000000..6c82f055 --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/OpeningsPerYearFiltersDto.java @@ -0,0 +1,6 @@ +package ca.bc.gov.restapi.results.postgres.dto; + +import java.time.LocalDateTime; + +public record OpeningsPerYearFiltersDto( + String orgUnit, String status, LocalDateTime entryDateStart, LocalDateTime entryDateEnd) {} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java new file mode 100644 index 00000000..097f1204 --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java @@ -0,0 +1,52 @@ +package ca.bc.gov.restapi.results.postgres.endpoint; + +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.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.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/dashboard-metrics") +@Tag(name = "Dashboard Metrics") +@RequiredArgsConstructor +public class DashboardMetricsEndpoint { + + private final DashboardMetricsService dashboardMetricsService; + + @GetMapping("/submission-trends") + public List getOpeningsSubmissionTrends( + @RequestParam(value = "orgUnit", required = false) String orgUnitCode, + @RequestParam(value = "status", required = false) String statusCode, + @RequestParam(value = "entryDateStart", required = false) String entryDateStart, + @RequestParam(value = "entryDateEnd", required = false) String entryDateEnd) { + LocalDateTime entryDateStartDate = null; + LocalDateTime entryDateEndDate = null; + DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + if (!Objects.isNull(entryDateStart)) { + LocalDate entryDateStartLd = LocalDate.parse(entryDateStart, fmt); + entryDateStartDate = entryDateStartLd.atStartOfDay(); + } + + if (!Objects.isNull(entryDateEnd)) { + LocalDate entryDateEndLd = LocalDate.parse(entryDateEnd, fmt); + entryDateEndDate = entryDateEndLd.atStartOfDay(); + } + + OpeningsPerYearFiltersDto filtersDto = + new OpeningsPerYearFiltersDto( + orgUnitCode, statusCode, entryDateStartDate, entryDateEndDate); + return dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto); + } +} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/OpeningsLastYearEntity.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/OpeningsLastYearEntity.java new file mode 100644 index 00000000..ee70d90f --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/OpeningsLastYearEntity.java @@ -0,0 +1,35 @@ +package ca.bc.gov.restapi.results.postgres.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.time.LocalDateTime; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Entity +@Table(name = "openings_last_year") +public class OpeningsLastYearEntity { + + @Id + @Column(name = "opening_id") + private String openingId; + + @Column(name = "opening_entry_userid", nullable = false) + private String userId; + + @Column(name = "entry_timestamp", nullable = false) + private LocalDateTime entryTimestamp; + + @Column(name = "update_timestamp", nullable = false) + private LocalDateTime updateTimestamp; + + @Column(name = "status_code", nullable = false) + private String status; + + @Column(name = "org_unit_code", nullable = false) + private String orgUnitCode; +} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepository.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepository.java new file mode 100644 index 00000000..9d1e0fcc --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepository.java @@ -0,0 +1,6 @@ +package ca.bc.gov.restapi.results.postgres.repository; + +import ca.bc.gov.restapi.results.postgres.entity.OpeningsLastYearEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface OpeningsLastYearRepository extends JpaRepository {} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsService.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsService.java new file mode 100644 index 00000000..e83f70c9 --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsService.java @@ -0,0 +1,96 @@ +package ca.bc.gov.restapi.results.postgres.service; + +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.entity.OpeningsLastYearEntity; +import ca.bc.gov.restapi.results.postgres.repository.OpeningsLastYearRepository; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +/** This class contains methods for gathering and grouping data for the dashboard metrics screen. */ +@Slf4j +@Service +@RequiredArgsConstructor +public class DashboardMetricsService { + + private final OpeningsLastYearRepository openingsLastYearRepository; + + /** + * Get openings submission trends data for the opening per year chart. + * + * @param filters Possible filter, see {@link OpeningsPerYearFiltersDto} for more. + * @return A list of {@link OpeningsPerYearDto} for the opening chart. + */ + public List getOpeningsSubmissionTrends(OpeningsPerYearFiltersDto filters) { + log.info("Getting Opening Submission Trends with filters {}", filters.toString()); + + List entities = + openingsLastYearRepository.findAll(Sort.by("entryTimestamp").descending()); + + if (entities.isEmpty()) { + log.info("No Opening Submission Trends data found!"); + return List.of(); + } + + Map> resultMap = new HashMap<>(); + + // Fill with 12 months + Integer monthValue = entities.get(0).getEntryTimestamp().getMonthValue(); + while (resultMap.size() < 12) { + resultMap.putIfAbsent(monthValue, new ArrayList<>()); + + monthValue += 1; + if (monthValue == 13) { + monthValue = 1; + } + }; + + for (OpeningsLastYearEntity entity : entities) { + // Org Unit filter - District + if (!Objects.isNull(filters.orgUnit()) + && !entity.getOrgUnitCode().equals(filters.orgUnit())) { + continue; + } + + // Status filter + if (!Objects.isNull(filters.status()) && !entity.getStatus().equals(filters.status())) { + continue; + } + + // Entry start date filter + if (!Objects.isNull(filters.entryDateStart()) + && entity.getEntryTimestamp().isBefore(filters.entryDateStart())) { + continue; + } + + // Entry end date filter + if (!Objects.isNull(filters.entryDateEnd()) + && entity.getEntryTimestamp().isAfter(filters.entryDateEnd())) { + continue; + } + + Integer month = entity.getEntryTimestamp().getMonthValue(); + resultMap.putIfAbsent(month, new ArrayList<>()); + + resultMap.get(month).add(entity); + } + + List chartData = new ArrayList<>(); + for (Map.Entry> entry : resultMap.entrySet()) { + chartData.add(new OpeningsPerYearDto(entry.getKey(), entry.getValue().size())); + } + + // Sort by month + chartData.sort(Comparator.comparing(OpeningsPerYearDto::month)); + + return chartData; + } +} diff --git a/backend/src/main/resources/db/migration/V4__update_metrics_charts_tables.sql b/backend/src/main/resources/db/migration/V4__update_metrics_charts_tables.sql new file mode 100644 index 00000000..3b1c3dba --- /dev/null +++ b/backend/src/main/resources/db/migration/V4__update_metrics_charts_tables.sql @@ -0,0 +1,9 @@ +ALTER TABLE + silva.openings_last_year +ADD + status_code VARCHAR(3) NOT NULL; + +ALTER TABLE + silva.openings_last_year +ADD + org_unit_code VARCHAR(6) NOT NULL; From a06aae24a11742ec14d4c5f102910af676c011ca Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Fri, 29 Mar 2024 11:22:07 -0300 Subject: [PATCH 02/19] test: dashboard submission trends endpoint test jira task SILVA-331 --- .../DashboardMetricsEndpointTest.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpointTest.java diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpointTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpointTest.java new file mode 100644 index 00000000..d64315e0 --- /dev/null +++ b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpointTest.java @@ -0,0 +1,69 @@ +package ca.bc.gov.restapi.results.postgres.endpoint; + +import static org.mockito.Mockito.when; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +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 java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest(DashboardMetricsEndpoint.class) +@WithMockUser +class DashboardMetricsEndpointTest { + + @Autowired private MockMvc mockMvc; + + @MockBean private DashboardMetricsService dashboardMetricsService; + + @Test + @DisplayName("Opening submission trends with no filters should succeed") + void getOpeningsSubmissionTrends_noFilters_shouldSucceed() throws Exception { + OpeningsPerYearFiltersDto filtersDto = new OpeningsPerYearFiltersDto(null, null, null, null); + + OpeningsPerYearDto dto = new OpeningsPerYearDto(1, "Jan", 70); + when(dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto)).thenReturn(List.of(dto)); + + mockMvc + .perform( + get("/api/dashboard-metrics/submission-trends") + .with(csrf().asHeader()) + .header("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$[0].month").value("1")) + .andExpect(jsonPath("$[0].monthName").value("Jan")) + .andExpect(jsonPath("$[0].amount").value("70")) + .andReturn(); + } + + @Test + @DisplayName("Opening submission trends with no data should succeed") + void getOpeningsSubmissionTrends_orgUnitFilter_shouldSucceed() throws Exception { + OpeningsPerYearFiltersDto filtersDto = new OpeningsPerYearFiltersDto("DCR", null, null, null); + + when(dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto)).thenReturn(List.of()); + + mockMvc + .perform( + get("/api/dashboard-metrics/submission-trends") + .with(csrf().asHeader()) + .header("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()) + .andReturn(); + } +} From b0d81a4118959d6f6027fba863daa3d575e019f3 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Fri, 29 Mar 2024 16:55:09 -0300 Subject: [PATCH 03/19] test: dashboard submission trends service test jira task SILVA-331 --- .../service/DashboardMetricsServiceTest.java | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java new file mode 100644 index 00000000..15f86a54 --- /dev/null +++ b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java @@ -0,0 +1,134 @@ +package ca.bc.gov.restapi.results.postgres.service; + +import static org.mockito.Mockito.when; + +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.entity.OpeningsLastYearEntity; +import ca.bc.gov.restapi.results.postgres.repository.OpeningsLastYearRepository; +import java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Sort; + +@ExtendWith(MockitoExtension.class) +public class DashboardMetricsServiceTest { + + @Mock OpeningsLastYearRepository openingsLastYearRepository; + + private DashboardMetricsService dashboardMetricsService; + + private static final Sort SORT = Sort.by("entryTimestamp").descending(); + + private List mockOpeningsEntityList() { + LocalDateTime entryTimestamp = LocalDateTime.now(); + + OpeningsLastYearEntity entity = new OpeningsLastYearEntity(); + entity.setOpeningId("123456"); + entity.setUserId("userId"); + entity.setEntryTimestamp(entryTimestamp); + entity.setUpdateTimestamp(entryTimestamp); + entity.setStatus("APP"); + entity.setOrgUnitCode("DCR"); + return List.of(entity); + } + + @BeforeEach + void setup() { + dashboardMetricsService = new DashboardMetricsService(openingsLastYearRepository); + } + + @Test + @DisplayName("Opening submission trends with no filters should succeed") + void getOpeningsSubmissionTrends_noFilters_shouldSucceed() throws Exception { + LocalDateTime now = LocalDateTime.now(); + Integer month = now.getMonthValue(); + String monthName = now.getMonth().name().toLowerCase(); + monthName = monthName.substring(0, 1).toUpperCase() + monthName.substring(1, 3); + + List entities = mockOpeningsEntityList(); + when(openingsLastYearRepository.findAll(SORT)).thenReturn(entities); + + OpeningsPerYearFiltersDto filtersDto = new OpeningsPerYearFiltersDto(null, null, null, null); + List list = dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto); + + Assertions.assertFalse(list.isEmpty()); + Assertions.assertEquals(12, list.size()); + Assertions.assertEquals(month, list.get(0).month()); + Assertions.assertEquals(monthName, list.get(0).monthName()); + Assertions.assertEquals(1, list.get(0).amount()); + } + + @Test + @DisplayName("Opening submission trends with Org Unit filter should succeed") + void getOpeningsSubmissionTrends_orgUnitFilter_shouldSucceed() throws Exception { + LocalDateTime now = LocalDateTime.now(); + Integer month = now.getMonthValue(); + String monthName = now.getMonth().name().toLowerCase(); + monthName = monthName.substring(0, 1).toUpperCase() + monthName.substring(1, 3); + + List entities = mockOpeningsEntityList(); + when(openingsLastYearRepository.findAll(SORT)).thenReturn(entities); + + OpeningsPerYearFiltersDto filtersDto = new OpeningsPerYearFiltersDto("AAA", null, null, null); + List list = dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto); + + Assertions.assertFalse(list.isEmpty()); + Assertions.assertEquals(12, list.size()); + Assertions.assertEquals(month, list.get(0).month()); + Assertions.assertEquals(monthName, list.get(0).monthName()); + Assertions.assertEquals(0, list.get(0).amount()); + } + + @Test + @DisplayName("Opening submission trends with Status filter should succeed") + void getOpeningsSubmissionTrends_statusFilter_shouldSucceed() { + LocalDateTime now = LocalDateTime.now(); + Integer month = now.getMonthValue(); + String monthName = now.getMonth().name().toLowerCase(); + monthName = monthName.substring(0, 1).toUpperCase() + monthName.substring(1, 3); + + List entities = mockOpeningsEntityList(); + when(openingsLastYearRepository.findAll(SORT)).thenReturn(entities); + + OpeningsPerYearFiltersDto filtersDto = new OpeningsPerYearFiltersDto(null, "APP", null, null); + List list = dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto); + + Assertions.assertFalse(list.isEmpty()); + Assertions.assertEquals(12, list.size()); + Assertions.assertEquals(month, list.get(0).month()); + Assertions.assertEquals(monthName, list.get(0).monthName()); + Assertions.assertEquals(1, list.get(0).amount()); + } + + @Test + @DisplayName("Opening submission trends with Status filter should succeed") + void getOpeningsSubmissionTrends_datesFilter_shouldSucceed() { + LocalDateTime now = LocalDateTime.now(); + Integer month = now.getMonthValue(); + String monthName = now.getMonth().name().toLowerCase(); + monthName = monthName.substring(0, 1).toUpperCase() + monthName.substring(1, 3); + + List entities = mockOpeningsEntityList(); + when(openingsLastYearRepository.findAll(SORT)).thenReturn(entities); + + LocalDateTime oneMonthBefore = now.minusMonths(1L); + LocalDateTime oneMonthLater = now.plusMonths(1L); + + OpeningsPerYearFiltersDto filtersDto = + new OpeningsPerYearFiltersDto(null, null, oneMonthBefore, oneMonthLater); + List list = dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto); + + Assertions.assertFalse(list.isEmpty()); + Assertions.assertEquals(12, list.size()); + Assertions.assertEquals(month, list.get(0).month()); + Assertions.assertEquals(monthName, list.get(0).monthName()); + Assertions.assertEquals(1, list.get(0).amount()); + } +} From 0c82486bfdccf479f99f84ec29cf4f611ced2b48 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Fri, 29 Mar 2024 18:11:39 -0300 Subject: [PATCH 04/19] chore: fix checkstyle issues and improve service logic --- .../postgres/dto/OpeningsPerYearDto.java | 3 +- .../dto/OpeningsPerYearFiltersDto.java | 1 + .../endpoint/DashboardMetricsEndpoint.java | 26 +++++++++++-- .../entity/OpeningsLastYearEntity.java | 1 + .../OpeningsLastYearRepository.java | 1 + .../service/DashboardMetricsService.java | 32 +++++++++------- .../V4__update_metrics_charts_tables.sql | 12 ++---- .../service/DashboardMetricsServiceTest.java | 38 +++++++++---------- 8 files changed, 65 insertions(+), 49 deletions(-) diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/OpeningsPerYearDto.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/OpeningsPerYearDto.java index 359f4902..3c6721d5 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/OpeningsPerYearDto.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/OpeningsPerYearDto.java @@ -6,5 +6,6 @@ @Schema( description = "This record represents a record for the \"Opening submission trends\" chart.") public record OpeningsPerYearDto( - @Schema(description = "The `x` value with the month name.", example = "Mar") Integer month, + @Schema(description = "The `x` value with the month number.", example = "3") Integer month, + @Schema(description = "The `x` value with the month name.", example = "Mar") String monthName, @Schema(description = "The `y` value with the month value.", example = "70") Integer amount) {} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/OpeningsPerYearFiltersDto.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/OpeningsPerYearFiltersDto.java index 6c82f055..5d2a5dec 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/OpeningsPerYearFiltersDto.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/OpeningsPerYearFiltersDto.java @@ -2,5 +2,6 @@ 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) {} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java index 097f1204..b549849f 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java @@ -9,13 +9,14 @@ 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; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +/** This class holds resources for the dashboard metrics page. */ @RestController @RequestMapping("/api/dashboard-metrics") @Tag(name = "Dashboard Metrics") @@ -24,8 +25,17 @@ public class DashboardMetricsEndpoint { private final DashboardMetricsService dashboardMetricsService; + /** + * Get data for the Submission Trends Chart, Openings per Year. + * + * @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. + * @return A list of values to populate the chart or 204 no content if no data. + */ @GetMapping("/submission-trends") - public List getOpeningsSubmissionTrends( + public ResponseEntity> getOpeningsSubmissionTrends( @RequestParam(value = "orgUnit", required = false) String orgUnitCode, @RequestParam(value = "status", required = false) String statusCode, @RequestParam(value = "entryDateStart", required = false) String entryDateStart, @@ -33,7 +43,7 @@ public List getOpeningsSubmissionTrends( LocalDateTime entryDateStartDate = null; LocalDateTime entryDateEndDate = null; DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd"); - + if (!Objects.isNull(entryDateStart)) { LocalDate entryDateStartLd = LocalDate.parse(entryDateStart, fmt); entryDateStartDate = entryDateStartLd.atStartOfDay(); @@ -47,6 +57,14 @@ public List getOpeningsSubmissionTrends( OpeningsPerYearFiltersDto filtersDto = new OpeningsPerYearFiltersDto( orgUnitCode, statusCode, entryDateStartDate, entryDateEndDate); - return dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto); + + List resultList = + dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto); + + if (resultList.isEmpty()) { + return ResponseEntity.noContent().build(); + } + + return ResponseEntity.ok(resultList); } } diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/OpeningsLastYearEntity.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/OpeningsLastYearEntity.java index ee70d90f..5950a4e5 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/OpeningsLastYearEntity.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/OpeningsLastYearEntity.java @@ -8,6 +8,7 @@ import lombok.Getter; import lombok.Setter; +/** This class represents a record in the database for the openings_last_year table. */ @Getter @Setter @Entity diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepository.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepository.java index 9d1e0fcc..418cd656 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepository.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepository.java @@ -3,4 +3,5 @@ import ca.bc.gov.restapi.results.postgres.entity.OpeningsLastYearEntity; import org.springframework.data.jpa.repository.JpaRepository; +/** This interface provides access to the database for the OpeningsLastYearEntity entity. */ public interface OpeningsLastYearRepository extends JpaRepository {} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsService.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsService.java index e83f70c9..09eee9dc 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsService.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsService.java @@ -4,9 +4,10 @@ import ca.bc.gov.restapi.results.postgres.dto.OpeningsPerYearFiltersDto; import ca.bc.gov.restapi.results.postgres.entity.OpeningsLastYearEntity; import ca.bc.gov.restapi.results.postgres.repository.OpeningsLastYearRepository; +import java.time.Month; import java.util.ArrayList; -import java.util.Comparator; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -33,25 +34,31 @@ public List getOpeningsSubmissionTrends(OpeningsPerYearFilte log.info("Getting Opening Submission Trends with filters {}", filters.toString()); List entities = - openingsLastYearRepository.findAll(Sort.by("entryTimestamp").descending()); + openingsLastYearRepository.findAll(Sort.by("entryTimestamp").ascending()); if (entities.isEmpty()) { log.info("No Opening Submission Trends data found!"); return List.of(); } - Map> resultMap = new HashMap<>(); + Map> resultMap = new LinkedHashMap<>(); + Map monthNamesMap = new HashMap<>(); // Fill with 12 months Integer monthValue = entities.get(0).getEntryTimestamp().getMonthValue(); + log.info("First month: {}", monthValue); while (resultMap.size() < 12) { - resultMap.putIfAbsent(monthValue, new ArrayList<>()); + resultMap.put(monthValue, new ArrayList<>()); + + String monthName = Month.of(monthValue).name().toLowerCase(); + monthName = monthName.substring(0, 1).toUpperCase() + monthName.substring(1, 3); + monthNamesMap.put(monthValue, monthName); monthValue += 1; if (monthValue == 13) { monthValue = 1; } - }; + } for (OpeningsLastYearEntity entity : entities) { // Org Unit filter - District @@ -77,20 +84,17 @@ public List getOpeningsSubmissionTrends(OpeningsPerYearFilte continue; } - Integer month = entity.getEntryTimestamp().getMonthValue(); - resultMap.putIfAbsent(month, new ArrayList<>()); - - resultMap.get(month).add(entity); + resultMap.get(entity.getEntryTimestamp().getMonthValue()).add(entity); } List chartData = new ArrayList<>(); - for (Map.Entry> entry : resultMap.entrySet()) { - chartData.add(new OpeningsPerYearDto(entry.getKey(), entry.getValue().size())); + for (Integer monthKey : resultMap.keySet()) { + List monthDataList = resultMap.get(monthKey); + String monthName = monthNamesMap.get(monthKey); + log.info("Month: {}", monthName); + chartData.add(new OpeningsPerYearDto(monthKey, monthName, monthDataList.size())); } - // Sort by month - chartData.sort(Comparator.comparing(OpeningsPerYearDto::month)); - return chartData; } } diff --git a/backend/src/main/resources/db/migration/V4__update_metrics_charts_tables.sql b/backend/src/main/resources/db/migration/V4__update_metrics_charts_tables.sql index 3b1c3dba..a7530659 100644 --- a/backend/src/main/resources/db/migration/V4__update_metrics_charts_tables.sql +++ b/backend/src/main/resources/db/migration/V4__update_metrics_charts_tables.sql @@ -1,9 +1,3 @@ -ALTER TABLE - silva.openings_last_year -ADD - status_code VARCHAR(3) NOT NULL; - -ALTER TABLE - silva.openings_last_year -ADD - org_unit_code VARCHAR(6) NOT NULL; +alter table silva.openings_last_year + add column status_code VARCHAR(3) NOT NULL, + add column org_unit_code VARCHAR(6) NOT NULL; diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java index 15f86a54..3390c6ca 100644 --- a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java +++ b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java @@ -18,7 +18,7 @@ import org.springframework.data.domain.Sort; @ExtendWith(MockitoExtension.class) -public class DashboardMetricsServiceTest { +class DashboardMetricsServiceTest { @Mock OpeningsLastYearRepository openingsLastYearRepository; @@ -48,19 +48,18 @@ void setup() { @DisplayName("Opening submission trends with no filters should succeed") void getOpeningsSubmissionTrends_noFilters_shouldSucceed() throws Exception { LocalDateTime now = LocalDateTime.now(); - Integer month = now.getMonthValue(); - String monthName = now.getMonth().name().toLowerCase(); - monthName = monthName.substring(0, 1).toUpperCase() + monthName.substring(1, 3); - List entities = mockOpeningsEntityList(); when(openingsLastYearRepository.findAll(SORT)).thenReturn(entities); OpeningsPerYearFiltersDto filtersDto = new OpeningsPerYearFiltersDto(null, null, null, null); List list = dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto); + String monthName = now.getMonth().name().toLowerCase(); + monthName = monthName.substring(0, 1).toUpperCase() + monthName.substring(1, 3); + Assertions.assertFalse(list.isEmpty()); Assertions.assertEquals(12, list.size()); - Assertions.assertEquals(month, list.get(0).month()); + Assertions.assertEquals(now.getMonthValue(), list.get(0).month()); Assertions.assertEquals(monthName, list.get(0).monthName()); Assertions.assertEquals(1, list.get(0).amount()); } @@ -69,19 +68,18 @@ void getOpeningsSubmissionTrends_noFilters_shouldSucceed() throws Exception { @DisplayName("Opening submission trends with Org Unit filter should succeed") void getOpeningsSubmissionTrends_orgUnitFilter_shouldSucceed() throws Exception { LocalDateTime now = LocalDateTime.now(); - Integer month = now.getMonthValue(); - String monthName = now.getMonth().name().toLowerCase(); - monthName = monthName.substring(0, 1).toUpperCase() + monthName.substring(1, 3); - List entities = mockOpeningsEntityList(); when(openingsLastYearRepository.findAll(SORT)).thenReturn(entities); OpeningsPerYearFiltersDto filtersDto = new OpeningsPerYearFiltersDto("AAA", null, null, null); List list = dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto); + String monthName = now.getMonth().name().toLowerCase(); + monthName = monthName.substring(0, 1).toUpperCase() + monthName.substring(1, 3); + Assertions.assertFalse(list.isEmpty()); Assertions.assertEquals(12, list.size()); - Assertions.assertEquals(month, list.get(0).month()); + Assertions.assertEquals(now.getMonthValue(), list.get(0).month()); Assertions.assertEquals(monthName, list.get(0).monthName()); Assertions.assertEquals(0, list.get(0).amount()); } @@ -90,19 +88,18 @@ void getOpeningsSubmissionTrends_orgUnitFilter_shouldSucceed() throws Exception @DisplayName("Opening submission trends with Status filter should succeed") void getOpeningsSubmissionTrends_statusFilter_shouldSucceed() { LocalDateTime now = LocalDateTime.now(); - Integer month = now.getMonthValue(); - String monthName = now.getMonth().name().toLowerCase(); - monthName = monthName.substring(0, 1).toUpperCase() + monthName.substring(1, 3); - List entities = mockOpeningsEntityList(); when(openingsLastYearRepository.findAll(SORT)).thenReturn(entities); OpeningsPerYearFiltersDto filtersDto = new OpeningsPerYearFiltersDto(null, "APP", null, null); List list = dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto); + String monthName = now.getMonth().name().toLowerCase(); + monthName = monthName.substring(0, 1).toUpperCase() + monthName.substring(1, 3); + Assertions.assertFalse(list.isEmpty()); Assertions.assertEquals(12, list.size()); - Assertions.assertEquals(month, list.get(0).month()); + Assertions.assertEquals(now.getMonthValue(), list.get(0).month()); Assertions.assertEquals(monthName, list.get(0).monthName()); Assertions.assertEquals(1, list.get(0).amount()); } @@ -111,10 +108,6 @@ void getOpeningsSubmissionTrends_statusFilter_shouldSucceed() { @DisplayName("Opening submission trends with Status filter should succeed") void getOpeningsSubmissionTrends_datesFilter_shouldSucceed() { LocalDateTime now = LocalDateTime.now(); - Integer month = now.getMonthValue(); - String monthName = now.getMonth().name().toLowerCase(); - monthName = monthName.substring(0, 1).toUpperCase() + monthName.substring(1, 3); - List entities = mockOpeningsEntityList(); when(openingsLastYearRepository.findAll(SORT)).thenReturn(entities); @@ -125,9 +118,12 @@ void getOpeningsSubmissionTrends_datesFilter_shouldSucceed() { new OpeningsPerYearFiltersDto(null, null, oneMonthBefore, oneMonthLater); List list = dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto); + String monthName = now.getMonth().name().toLowerCase(); + monthName = monthName.substring(0, 1).toUpperCase() + monthName.substring(1, 3); + Assertions.assertFalse(list.isEmpty()); Assertions.assertEquals(12, list.size()); - Assertions.assertEquals(month, list.get(0).month()); + Assertions.assertEquals(now.getMonthValue(), list.get(0).month()); Assertions.assertEquals(monthName, list.get(0).monthName()); Assertions.assertEquals(1, list.get(0).amount()); } From 23f0f46ab5aa68e222ace0115bb9bac7a8c35cbb Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Fri, 29 Mar 2024 18:14:33 -0300 Subject: [PATCH 05/19] test: fix test cases failing --- .../results/postgres/service/DashboardMetricsServiceTest.java | 2 +- backend/src/test/resources/application.properties | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java index 3390c6ca..85203c09 100644 --- a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java +++ b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java @@ -24,7 +24,7 @@ class DashboardMetricsServiceTest { private DashboardMetricsService dashboardMetricsService; - private static final Sort SORT = Sort.by("entryTimestamp").descending(); + private static final Sort SORT = Sort.by("entryTimestamp").ascending(); private List mockOpeningsEntityList() { LocalDateTime entryTimestamp = LocalDateTime.now(); diff --git a/backend/src/test/resources/application.properties b/backend/src/test/resources/application.properties index 16d57820..99759ac4 100644 --- a/backend/src/test/resources/application.properties +++ b/backend/src/test/resources/application.properties @@ -15,3 +15,5 @@ spring.jpa.hibernate.ddl-auto = create-drop # FAM spring.security.oauth2.resourceserver.jwt.issuer-uri = https://aws-cognito-issuer-uri.aws.com spring.security.oauth2.resourceserver.jwt.jwk-set-uri = https://aws-cognito-issuer-uri.aws.com/.well-known/jwks.json + +spring.flyway.enabled = false \ No newline at end of file From 9e00586b6f72d4cadea99f4f436f8a38cfc85cbc Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Fri, 29 Mar 2024 18:29:54 -0300 Subject: [PATCH 06/19] docs: add swagger open-api docs to the endpoint --- .../endpoint/DashboardMetricsEndpoint.java | 59 +++++++++++++++++-- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java index b549849f..0c48ad1f 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java @@ -3,6 +3,12 @@ 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; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +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; @@ -19,7 +25,7 @@ /** This class holds resources for the dashboard metrics page. */ @RestController @RequestMapping("/api/dashboard-metrics") -@Tag(name = "Dashboard Metrics") +@Tag(name = "Dashboard Metrics", description = "Resources fot the Dashboard metrics charts") @RequiredArgsConstructor public class DashboardMetricsEndpoint { @@ -35,11 +41,54 @@ public class DashboardMetricsEndpoint { * @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.", + responses = { + @ApiResponse( + responseCode = "200", + description = "An array with twelve objects for the last 12 months."), + @ApiResponse( + responseCode = "204", + description = "No data found in the dable. No response body."), + @ApiResponse( + responseCode = "401", + description = "Access token is missing or invalid", + content = @Content(schema = @Schema(implementation = Void.class))) + }) public ResponseEntity> getOpeningsSubmissionTrends( - @RequestParam(value = "orgUnit", required = false) String orgUnitCode, - @RequestParam(value = "status", required = false) String statusCode, - @RequestParam(value = "entryDateStart", required = false) String entryDateStart, - @RequestParam(value = "entryDateEnd", required = false) String entryDateEnd) { + @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 = "statusCode", required = false) + @Parameter( + name = "statusCode", + in = ParameterIn.QUERY, + description = "The Openins Status code to filter", + required = false, + example = "APP") + String statusCode, + @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) { LocalDateTime entryDateStartDate = null; LocalDateTime entryDateEndDate = null; DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd"); From d82756902af1eab74e1748cf0327e9fdc41493a0 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Tue, 2 Apr 2024 17:35:33 -0300 Subject: [PATCH 07/19] feat: create api to handle favourite openings jira task silva-358 --- .../exception/NotFoundGenericException.java | 14 +++ .../exception/OpeningNotFoundException.java | 9 ++ .../UserOpeningNotFoundException.java | 9 ++ .../postgres/dto/UserOpeningCreateDto.java | 4 + .../endpoint/UserOpeningEndpoint.java | 96 ++++++++++++++-- .../OpeningsLastYearRepository.java | 8 +- .../repository/UserOpeningRepository.java | 10 +- .../postgres/service/UserOpeningService.java | 103 ++++++++++++++++++ 8 files changed, 244 insertions(+), 9 deletions(-) create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/common/exception/NotFoundGenericException.java create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/common/exception/OpeningNotFoundException.java create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/common/exception/UserOpeningNotFoundException.java create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/UserOpeningCreateDto.java create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningService.java diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/common/exception/NotFoundGenericException.java b/backend/src/main/java/ca/bc/gov/restapi/results/common/exception/NotFoundGenericException.java new file mode 100644 index 00000000..417c8efd --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/common/exception/NotFoundGenericException.java @@ -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)); + } +} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/common/exception/OpeningNotFoundException.java b/backend/src/main/java/ca/bc/gov/restapi/results/common/exception/OpeningNotFoundException.java new file mode 100644 index 00000000..75bee872 --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/common/exception/OpeningNotFoundException.java @@ -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"); + } +} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/common/exception/UserOpeningNotFoundException.java b/backend/src/main/java/ca/bc/gov/restapi/results/common/exception/UserOpeningNotFoundException.java new file mode 100644 index 00000000..a6d69255 --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/common/exception/UserOpeningNotFoundException.java @@ -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"); + } +} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/UserOpeningCreateDto.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/UserOpeningCreateDto.java new file mode 100644 index 00000000..32150a96 --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/UserOpeningCreateDto.java @@ -0,0 +1,4 @@ +package ca.bc.gov.restapi.results.postgres.dto; + +/** This record represents the JSON body request for saving Openings as favourite. */ +public record UserOpeningCreateDto(String openingId) {} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpoint.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpoint.java index 0b156b38..aa4078c0 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpoint.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpoint.java @@ -1,12 +1,26 @@ package ca.bc.gov.restapi.results.postgres.endpoint; +import ca.bc.gov.restapi.results.postgres.dto.UserOpeningCreateDto; import ca.bc.gov.restapi.results.postgres.entity.UserOpeningEntity; -import ca.bc.gov.restapi.results.postgres.repository.UserOpeningRepository; +import ca.bc.gov.restapi.results.postgres.service.UserOpeningService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.Content; +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.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** This class holds resources for exposing user openings saved as favourites. */ @@ -15,16 +29,84 @@ @Tag( name = "User Opennings (SILVA)", description = "Endpoints to handle user favourite Openings in the `SILVA` schema.") +@RequiredArgsConstructor public class UserOpeningEndpoint { - private final UserOpeningRepository userOpeningRepository; + private final UserOpeningService userOpeningService; - UserOpeningEndpoint(UserOpeningRepository userOpeningRepository) { - this.userOpeningRepository = userOpeningRepository; + /** + * Gets all saved Openings to a user. + * + * @return A list of openings or the http code 204-No Content. + */ + @GetMapping + @Operation( + summary = "Gets all saved Openings to a user", + description = "Gets all openings highlighted by the user, saved as favourite.", + responses = { + @ApiResponse( + responseCode = "200", + description = "An array containing one or more Openings found for the user."), + @ApiResponse( + responseCode = "204", + description = "No data found in the dable. No response body."), + @ApiResponse( + responseCode = "401", + description = "Access token is missing or invalid", + content = @Content(schema = @Schema(implementation = Void.class))) + }) + public ResponseEntity> getAll() { + List userOpenings = userOpeningService.getAllUserOpenings(); + if (userOpenings.isEmpty()) { + return ResponseEntity.noContent().build(); + } + + return ResponseEntity.ok(userOpenings); } - @GetMapping - public List getAll() { - return userOpeningRepository.findAll(); + /** + * Saves one or more Opening ID as favourite to an user. + * + * @param createDtos List of {@link UserOpeningCreateDto} with opening ids + * @return HTTP status code 201 if success, no response body. + */ + @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) + @Operation( + summary = "Saves one or more Opening ID as favourite to an user", + description = "Allow users to save on or more Opening IDs at the same request as favourite.", + responses = { + @ApiResponse( + responseCode = "201", + description = "All opening ids were found and successfully saved to the user."), + @ApiResponse( + responseCode = "401", + description = "Access token is missing or invalid", + content = @Content(schema = @Schema(implementation = Void.class))), + @ApiResponse( + responseCode = "404", + description = "One or more openings were not found in the system.") + }) + public ResponseEntity saveUserOpening(@RequestBody List createDtos) { + userOpeningService.saveOpeningsToUser(createDtos); + return ResponseEntity.status(HttpStatus.CREATED).build(); + } + + /** + * Deletes one or more user openings from their favourite list. + * + * @param openingIds List with one or more Opening IDs. + * @return HTTP status code 204 if success, no response body. + */ + @DeleteMapping + public ResponseEntity deleteUserOpening( + @Parameter( + name = "openingIds", + in = ParameterIn.QUERY, + description = "Opening ID list separated by comma", + required = true) + @RequestParam + List openingIds) { + userOpeningService.deleteOpeningsFromUserFavourite(openingIds); + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } } diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepository.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepository.java index 418cd656..f4fc8de7 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepository.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepository.java @@ -1,7 +1,13 @@ package ca.bc.gov.restapi.results.postgres.repository; import ca.bc.gov.restapi.results.postgres.entity.OpeningsLastYearEntity; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; /** This interface provides access to the database for the OpeningsLastYearEntity entity. */ -public interface OpeningsLastYearRepository extends JpaRepository {} +public interface OpeningsLastYearRepository extends JpaRepository { + + @Query("from OpeningsLastYearEntity o where o.openingId in ?1") + List findAllByOpeningIdInList(List openingIdList); +} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/UserOpeningRepository.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/UserOpeningRepository.java index fb208a22..377be33c 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/UserOpeningRepository.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/UserOpeningRepository.java @@ -2,8 +2,16 @@ import ca.bc.gov.restapi.results.postgres.entity.UserOpeningEntity; import ca.bc.gov.restapi.results.postgres.entity.UserOpeningEntityId; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; /** This interface holds methods for handling {@link UserOpeningEntity} data in the database. */ public interface UserOpeningRepository - extends JpaRepository {} + extends JpaRepository { + + List findAllByUserId(String userId); + + @Query("from UserOpeningEntity o where o.openingId in ?1 and o.userId = ?2") + List findAllByOpeningIdInAndUserId(List openingIdList, String userId); +} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningService.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningService.java new file mode 100644 index 00000000..d81201f0 --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningService.java @@ -0,0 +1,103 @@ +package ca.bc.gov.restapi.results.postgres.service; + +import ca.bc.gov.restapi.results.common.exception.OpeningNotFoundException; +import ca.bc.gov.restapi.results.common.exception.UserOpeningNotFoundException; +import ca.bc.gov.restapi.results.common.security.LoggedUserService; +import ca.bc.gov.restapi.results.postgres.dto.UserOpeningCreateDto; +import ca.bc.gov.restapi.results.postgres.entity.OpeningsLastYearEntity; +import ca.bc.gov.restapi.results.postgres.entity.UserOpeningEntity; +import ca.bc.gov.restapi.results.postgres.repository.OpeningsLastYearRepository; +import ca.bc.gov.restapi.results.postgres.repository.UserOpeningRepository; +import jakarta.transaction.Transactional; +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** This class contains methods for handling User favourite Openings. */ +@Slf4j +@Service +@RequiredArgsConstructor +public class UserOpeningService { + + private final LoggedUserService loggedUserService; + + private final UserOpeningRepository userOpeningRepository; + + private final OpeningsLastYearRepository openingsLastYearRepository; + + /** + * Gets all openings saved for an user. + * + * @return A list of {@link UserOpeningEntity} containing the found records. + */ + public List getAllUserOpenings() { + String userId = loggedUserService.getLoggedUserId(); + return userOpeningRepository.findAllByUserId(userId); + } + + /** + * Saves one or more Openings IDs to an user. + * + * @param createDtos List with one ore more opening IDs. + */ + @Transactional + public void saveOpeningsToUser(List createDtos) { + log.info("{} Opening IDs to save in the favourites!", createDtos.size()); + + // validate all openings + List openingIdList = createDtos.stream().map(UserOpeningCreateDto::openingId).toList(); + + log.info("Looking for Openings in the spar.openings_last_year table!"); + List entities = + openingsLastYearRepository.findAllByOpeningIdInList(openingIdList); + + if (entities.size() < createDtos.size()) { + log.info( + "Result list ({}) contains less records than Openings IDs received ({})!", + entities.size(), + createDtos.size()); + throw new OpeningNotFoundException(); + } + + final String userId = loggedUserService.getLoggedUserId(); + + List entitiesToSave = new ArrayList<>(); + createDtos.forEach( + dto -> { + UserOpeningEntity entity = new UserOpeningEntity(); + entity.setUserId(userId); + entity.setOpeningId(dto.openingId()); + entitiesToSave.add(entity); + }); + + userOpeningRepository.saveAllAndFlush(entitiesToSave); + log.info("Opening IDs saved in the favourites!"); + } + + /** + * Deletes one or more user opening from favourite. + * + * @param openingIds List with opening IDs to be deleted. + */ + @Transactional + public void deleteOpeningsFromUserFavourite(List openingIds) { + log.info("{} Opening IDs to delete from the favourites!", openingIds.size()); + String userId = loggedUserService.getLoggedUserId(); + + List userOpenings = + userOpeningRepository.findAllByOpeningIdInAndUserId(openingIds, userId); + + if (userOpenings.size() < openingIds.size()) { + log.info( + "UserOpeningEntity result list ({}) contains less records than IDs received ({})!", + userOpenings.size(), + openingIds.size()); + throw new UserOpeningNotFoundException(); + } + + userOpeningRepository.deleteAll(userOpenings); + log.info("Opening IDs deleted from the favourites!"); + } +} From 3aa23054b1073f8ac267c97a33865e8304bdb45a Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Tue, 2 Apr 2024 17:45:54 -0300 Subject: [PATCH 08/19] test: add base files for test cases - wip --- .../results/postgres/endpoint/UserOpeningEndpointTest.java | 5 +++++ .../OpeningsLastYearRepositoryIntegrationTest.java | 5 +++++ .../repository/UserOpeningRepositoryIntegrationTest.java | 5 +++++ .../results/postgres/service/UserOpeningServiceTest.java | 5 +++++ 4 files changed, 20 insertions(+) create mode 100644 backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpointTest.java create mode 100644 backend/src/test/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepositoryIntegrationTest.java create mode 100644 backend/src/test/java/ca/bc/gov/restapi/results/postgres/repository/UserOpeningRepositoryIntegrationTest.java create mode 100644 backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningServiceTest.java diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpointTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpointTest.java new file mode 100644 index 00000000..4c917e66 --- /dev/null +++ b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpointTest.java @@ -0,0 +1,5 @@ +package ca.bc.gov.restapi.results.postgres.endpoint; + +class UserOpeningEndpointTest { + +} diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepositoryIntegrationTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepositoryIntegrationTest.java new file mode 100644 index 00000000..1c1ab26a --- /dev/null +++ b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepositoryIntegrationTest.java @@ -0,0 +1,5 @@ +package ca.bc.gov.restapi.results.postgres.repository; + +class OpeningsLastYearRepositoryIntegrationTest { + +} diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/repository/UserOpeningRepositoryIntegrationTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/repository/UserOpeningRepositoryIntegrationTest.java new file mode 100644 index 00000000..e644b25d --- /dev/null +++ b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/repository/UserOpeningRepositoryIntegrationTest.java @@ -0,0 +1,5 @@ +package ca.bc.gov.restapi.results.postgres.repository; + +class UserOpeningRepositoryIntegrationTest { + +} diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningServiceTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningServiceTest.java new file mode 100644 index 00000000..13562413 --- /dev/null +++ b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningServiceTest.java @@ -0,0 +1,5 @@ +package ca.bc.gov.restapi.results.postgres.service; + +class UserOpeningServiceTest { + +} From 6da9fe24304558576b3cd543b7a94d93d577104b Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Tue, 2 Apr 2024 22:39:04 -0300 Subject: [PATCH 09/19] feat: free growing milestone declarations api jira task silva-359 --- .../results/common/util/TimestampUtil.java | 26 +++++++ .../dto/FreeGrowingMilestonesDto.java | 6 ++ .../dto/OpeningsPerYearFiltersDto.java | 6 +- .../endpoint/DashboardMetricsEndpoint.java | 78 ++++++++++++++----- .../service/DashboardMetricsService.java | 14 ++++ 5 files changed, 110 insertions(+), 20 deletions(-) create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/common/util/TimestampUtil.java create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/FreeGrowingMilestonesDto.java diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/common/util/TimestampUtil.java b/backend/src/main/java/ca/bc/gov/restapi/results/common/util/TimestampUtil.java new file mode 100644 index 00000000..06be8a47 --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/common/util/TimestampUtil.java @@ -0,0 +1,26 @@ +package ca.bc.gov.restapi.results.common.util; + +import java.time.LocalDate; +import java.time.LocalDateTime; +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(); + } +} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/FreeGrowingMilestonesDto.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/FreeGrowingMilestonesDto.java new file mode 100644 index 00000000..3a38845d --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/FreeGrowingMilestonesDto.java @@ -0,0 +1,6 @@ +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(String label, BigDecimal percentage) {} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/OpeningsPerYearFiltersDto.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/OpeningsPerYearFiltersDto.java index 5d2a5dec..89271d64 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/OpeningsPerYearFiltersDto.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/OpeningsPerYearFiltersDto.java @@ -4,4 +4,8 @@ /** This record contains all possible filters for the dashboard openings per years api. */ public record OpeningsPerYearFiltersDto( - String orgUnit, String status, LocalDateTime entryDateStart, LocalDateTime entryDateEnd) {} + String orgUnit, + String status, + LocalDateTime entryDateStart, + LocalDateTime entryDateEnd, + String clientNumber) {} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java index 0c48ad1f..66017db6 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java @@ -1,5 +1,7 @@ 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.FreeGrowingMilestonesDto; 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; @@ -10,11 +12,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; @@ -89,23 +87,13 @@ public ResponseEntity> getOpeningsSubmissionTrends( required = false, example = "2024-03-11") String entryDateEnd) { - LocalDateTime entryDateStartDate = null; - LocalDateTime entryDateEndDate = null; - DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd"); - - if (!Objects.isNull(entryDateStart)) { - LocalDate entryDateStartLd = LocalDate.parse(entryDateStart, fmt); - entryDateStartDate = entryDateStartLd.atStartOfDay(); - } - - if (!Objects.isNull(entryDateEnd)) { - LocalDate entryDateEndLd = LocalDate.parse(entryDateEnd, fmt); - entryDateEndDate = entryDateEndLd.atStartOfDay(); - } - OpeningsPerYearFiltersDto filtersDto = new OpeningsPerYearFiltersDto( - orgUnitCode, statusCode, entryDateStartDate, entryDateEndDate); + orgUnitCode, + statusCode, + TimestampUtil.parseDateString(entryDateStart), + TimestampUtil.parseDateString(entryDateEnd), + null); List resultList = dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto); @@ -116,4 +104,56 @@ public ResponseEntity> getOpeningsSubmissionTrends( return ResponseEntity.ok(resultList); } + + // add open api docs here + @GetMapping("/free-growing-milestones") + public ResponseEntity> 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) { + OpeningsPerYearFiltersDto filtersDto = + new OpeningsPerYearFiltersDto( + orgUnitCode, + null, + TimestampUtil.parseDateString(entryDateStart), + TimestampUtil.parseDateString(entryDateEnd), + clientNumber); + List milestonesDto = + dashboardMetricsService.getFreeGrowingMilestoneChartData(filtersDto); + + if (milestonesDto.isEmpty()) { + return ResponseEntity.noContent().build(); + } + + return ResponseEntity.ok(milestonesDto); + } } diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsService.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsService.java index 09eee9dc..748d7cc6 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsService.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsService.java @@ -1,5 +1,6 @@ package ca.bc.gov.restapi.results.postgres.service; +import ca.bc.gov.restapi.results.postgres.dto.FreeGrowingMilestonesDto; 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.entity.OpeningsLastYearEntity; @@ -97,4 +98,17 @@ public List getOpeningsSubmissionTrends(OpeningsPerYearFilte return chartData; } + + /** + * Get free growing milestone declarations data for the chart. + * + * @param filtersDto Possible filter, see {@link OpeningsPerYearFiltersDto} for more. + * @return A list of {@link FreeGrowingMilestonesDto} for the chart. + */ + public List getFreeGrowingMilestoneChartData( + OpeningsPerYearFiltersDto filtersDto) { + // keep going from here + // rename OpeningsPerYearFiltersDto record to a generic name + return List.of(); + } } From 7c7398a4213c522b4a658d73cf09bcab6ba50223 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Fri, 5 Apr 2024 10:00:52 -0300 Subject: [PATCH 10/19] chore: improve wording and tests --- .../postgres/endpoint/DashboardMetricsEndpoint.java | 6 ++++-- .../postgres/endpoint/UserOpeningEndpoint.java | 2 +- .../endpoint/DashboardMetricsEndpointTest.java | 6 ++++-- .../postgres/service/DashboardMetricsServiceTest.java | 11 +++++++---- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java index 66017db6..d37a8e85 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java @@ -23,7 +23,9 @@ /** 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", + description = "Endpoints fot the Dashboard metrics charts in the `SILVA` schema") @RequiredArgsConstructor public class DashboardMetricsEndpoint { @@ -48,7 +50,7 @@ public class DashboardMetricsEndpoint { description = "An array with twelve objects for the last 12 months."), @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", diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpoint.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpoint.java index aa4078c0..06c0aa7e 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpoint.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpoint.java @@ -27,7 +27,7 @@ @RestController @RequestMapping(path = "/api/user-openings", produces = MediaType.APPLICATION_JSON_VALUE) @Tag( - name = "User Opennings (SILVA)", + name = "User Openings (SILVA)", description = "Endpoints to handle user favourite Openings in the `SILVA` schema.") @RequiredArgsConstructor public class UserOpeningEndpoint { diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpointTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpointTest.java index d64315e0..fc7abb12 100644 --- a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpointTest.java +++ b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpointTest.java @@ -31,7 +31,8 @@ class DashboardMetricsEndpointTest { @Test @DisplayName("Opening submission trends with no filters should succeed") void getOpeningsSubmissionTrends_noFilters_shouldSucceed() throws Exception { - OpeningsPerYearFiltersDto filtersDto = new OpeningsPerYearFiltersDto(null, null, null, null); + OpeningsPerYearFiltersDto filtersDto = + new OpeningsPerYearFiltersDto(null, null, null, null, null); OpeningsPerYearDto dto = new OpeningsPerYearDto(1, "Jan", 70); when(dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto)).thenReturn(List.of(dto)); @@ -53,7 +54,8 @@ void getOpeningsSubmissionTrends_noFilters_shouldSucceed() throws Exception { @Test @DisplayName("Opening submission trends with no data should succeed") void getOpeningsSubmissionTrends_orgUnitFilter_shouldSucceed() throws Exception { - OpeningsPerYearFiltersDto filtersDto = new OpeningsPerYearFiltersDto("DCR", null, null, null); + OpeningsPerYearFiltersDto filtersDto = + new OpeningsPerYearFiltersDto("DCR", null, null, null, null); when(dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto)).thenReturn(List.of()); diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java index 85203c09..2d2c979e 100644 --- a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java +++ b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java @@ -51,7 +51,8 @@ void getOpeningsSubmissionTrends_noFilters_shouldSucceed() throws Exception { List entities = mockOpeningsEntityList(); when(openingsLastYearRepository.findAll(SORT)).thenReturn(entities); - OpeningsPerYearFiltersDto filtersDto = new OpeningsPerYearFiltersDto(null, null, null, null); + OpeningsPerYearFiltersDto filtersDto = + new OpeningsPerYearFiltersDto(null, null, null, null, null); List list = dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto); String monthName = now.getMonth().name().toLowerCase(); @@ -71,7 +72,8 @@ void getOpeningsSubmissionTrends_orgUnitFilter_shouldSucceed() throws Exception List entities = mockOpeningsEntityList(); when(openingsLastYearRepository.findAll(SORT)).thenReturn(entities); - OpeningsPerYearFiltersDto filtersDto = new OpeningsPerYearFiltersDto("AAA", null, null, null); + OpeningsPerYearFiltersDto filtersDto = + new OpeningsPerYearFiltersDto("AAA", null, null, null, null); List list = dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto); String monthName = now.getMonth().name().toLowerCase(); @@ -91,7 +93,8 @@ void getOpeningsSubmissionTrends_statusFilter_shouldSucceed() { List entities = mockOpeningsEntityList(); when(openingsLastYearRepository.findAll(SORT)).thenReturn(entities); - OpeningsPerYearFiltersDto filtersDto = new OpeningsPerYearFiltersDto(null, "APP", null, null); + OpeningsPerYearFiltersDto filtersDto = + new OpeningsPerYearFiltersDto(null, "APP", null, null, null); List list = dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto); String monthName = now.getMonth().name().toLowerCase(); @@ -115,7 +118,7 @@ void getOpeningsSubmissionTrends_datesFilter_shouldSucceed() { LocalDateTime oneMonthLater = now.plusMonths(1L); OpeningsPerYearFiltersDto filtersDto = - new OpeningsPerYearFiltersDto(null, null, oneMonthBefore, oneMonthLater); + new OpeningsPerYearFiltersDto(null, null, oneMonthBefore, oneMonthLater, null); List list = dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto); String monthName = now.getMonth().name().toLowerCase(); From 58d723e6c3bcfc3d7a200a29a14c6e6ec6544358 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Mon, 8 Apr 2024 16:20:58 -0300 Subject: [PATCH 11/19] test: add opening last year api integration tests silva issue silva-358 --- ...ingsLastYearRepositoryIntegrationTest.java | 35 ++++++++++++++++++- ...ningsLastYearRepositoryIntegrationTest.sql | 13 +++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 backend/src/test/resources/sql_scripts/OpeningsLastYearRepositoryIntegrationTest.sql diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepositoryIntegrationTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepositoryIntegrationTest.java index 1c1ab26a..ee60eefe 100644 --- a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepositoryIntegrationTest.java +++ b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepositoryIntegrationTest.java @@ -1,5 +1,38 @@ package ca.bc.gov.restapi.results.postgres.repository; +import ca.bc.gov.restapi.results.postgres.entity.OpeningsLastYearEntity; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.test.context.jdbc.Sql; + +@DataJpaTest +@AutoConfigureTestDatabase(replace = Replace.NONE) +@Sql(scripts = {"classpath:sql_scripts/OpeningsLastYearRepositoryIntegrationTest.sql"}) class OpeningsLastYearRepositoryIntegrationTest { - + + @Autowired private OpeningsLastYearRepository openingsLastYearRepository; + + @Test + @DisplayName("find all by Opening ID in List") + void findAllByOpeningIdInListTest() { + List idList = List.of("7012511", "7012512", "7012513"); + List openingList = + openingsLastYearRepository.findAllByOpeningIdInList(idList); + + Assertions.assertFalse(openingList.isEmpty()); + Assertions.assertEquals(3, openingList.size()); + + OpeningsLastYearEntity first = openingList.get(0); + + Assertions.assertEquals("7012511", first.getOpeningId()); + Assertions.assertEquals("TEST", first.getUserId()); + Assertions.assertEquals("APP", first.getStatus()); + Assertions.assertEquals("DCR", first.getOrgUnitCode()); + } } diff --git a/backend/src/test/resources/sql_scripts/OpeningsLastYearRepositoryIntegrationTest.sql b/backend/src/test/resources/sql_scripts/OpeningsLastYearRepositoryIntegrationTest.sql new file mode 100644 index 00000000..99d82469 --- /dev/null +++ b/backend/src/test/resources/sql_scripts/OpeningsLastYearRepositoryIntegrationTest.sql @@ -0,0 +1,13 @@ +insert into openings_last_year ( + opening_id + ,opening_entry_userid + ,entry_timestamp + ,update_timestamp + ,status_code + ,org_unit_code +) values + ('7012511', 'TEST', '2024-04-05', '2024-04-08', 'APP', 'DCR'), + ('7012512', 'TEST', '2024-02-03', '2024-03-08', 'APP', 'DCR'), + ('7012513', 'TEST', '2024-03-04', '2024-04-08', 'APP', 'DCR'), + ('7012514', 'TEST', '2024-01-15', '2024-02-15', 'APP', 'DCR'), + ('7012515', 'TEST', '2024-02-18', '2024-02-25', 'APP', 'DCR'); From a9bbe3159907fd0b832aecb60c9534750c988852 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Tue, 9 Apr 2024 10:22:43 -0300 Subject: [PATCH 12/19] feat: free growing chart api jira issue silva-358 --- .../results/common/util/TimestampUtil.java | 28 ++++++ ...iltersDto.java => DashboardFiltesDto.java} | 2 +- .../dto/FreeGrowingMilestonesDto.java | 2 +- .../endpoint/DashboardMetricsEndpoint.java | 50 ++++++++--- .../entity/OpeningsLastYearEntity.java | 3 + .../service/DashboardMetricsService.java | 90 +++++++++++++++++-- .../V5__add_client_number_metrics_table.sql | 2 + .../DashboardMetricsEndpointTest.java | 8 +- .../service/DashboardMetricsServiceTest.java | 15 ++-- 9 files changed, 162 insertions(+), 38 deletions(-) rename backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/{OpeningsPerYearFiltersDto.java => DashboardFiltesDto.java} (88%) create mode 100644 backend/src/main/resources/db/migration/V5__add_client_number_metrics_table.sql diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/common/util/TimestampUtil.java b/backend/src/main/java/ca/bc/gov/restapi/results/common/util/TimestampUtil.java index 06be8a47..f889d243 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/common/util/TimestampUtil.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/common/util/TimestampUtil.java @@ -2,6 +2,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.Period; import java.time.format.DateTimeFormatter; import java.util.Objects; @@ -23,4 +24,31 @@ public static LocalDateTime parseDateString(String dateStr) { 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 + */ + 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 months = diff.getMonths(); + if (months <= 5) { + return 0; + } else if (months <= 11) { + return 1; + } else if (months <= 17) { + return 2; + } else { + return 3; + } + } } diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/OpeningsPerYearFiltersDto.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/DashboardFiltesDto.java similarity index 88% rename from backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/OpeningsPerYearFiltersDto.java rename to backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/DashboardFiltesDto.java index 89271d64..d6da148e 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/OpeningsPerYearFiltersDto.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/DashboardFiltesDto.java @@ -3,7 +3,7 @@ import java.time.LocalDateTime; /** This record contains all possible filters for the dashboard openings per years api. */ -public record OpeningsPerYearFiltersDto( +public record DashboardFiltesDto( String orgUnit, String status, LocalDateTime entryDateStart, diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/FreeGrowingMilestonesDto.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/FreeGrowingMilestonesDto.java index 3a38845d..0e24ef82 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/FreeGrowingMilestonesDto.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/FreeGrowingMilestonesDto.java @@ -3,4 +3,4 @@ import java.math.BigDecimal; /** This record represent a slice of the free growing milestone chart. */ -public record FreeGrowingMilestonesDto(String label, BigDecimal percentage) {} +public record FreeGrowingMilestonesDto(Integer index, String label, BigDecimal percentage) {} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java index d37a8e85..28f59e49 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java @@ -1,9 +1,9 @@ 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.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; @@ -32,18 +32,19 @@ 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", @@ -89,8 +90,8 @@ public ResponseEntity> getOpeningsSubmissionTrends( required = false, example = "2024-03-11") String entryDateEnd) { - OpeningsPerYearFiltersDto filtersDto = - new OpeningsPerYearFiltersDto( + DashboardFiltesDto filtersDto = + new DashboardFiltesDto( orgUnitCode, statusCode, TimestampUtil.parseDateString(entryDateStart), @@ -107,8 +108,31 @@ public ResponseEntity> getOpeningsSubmissionTrends( return ResponseEntity.ok(resultList); } - // add open api docs here + /** + * 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."), + @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> getFreeGrowingMilestonesData( @RequestParam(value = "orgUnitCode", required = false) @Parameter( @@ -142,8 +166,8 @@ public ResponseEntity> getFreeGrowingMilestonesDa required = false, example = "2024-03-11") String entryDateEnd) { - OpeningsPerYearFiltersDto filtersDto = - new OpeningsPerYearFiltersDto( + DashboardFiltesDto filtersDto = + new DashboardFiltesDto( orgUnitCode, null, TimestampUtil.parseDateString(entryDateStart), diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/OpeningsLastYearEntity.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/OpeningsLastYearEntity.java index 5950a4e5..061bc01e 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/OpeningsLastYearEntity.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/OpeningsLastYearEntity.java @@ -33,4 +33,7 @@ public class OpeningsLastYearEntity { @Column(name = "org_unit_code", nullable = false) private String orgUnitCode; + + @Column(name = "client_number", nullable = false) + private String clientNumber; } diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsService.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsService.java index 748d7cc6..3a0e2e0b 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsService.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsService.java @@ -1,10 +1,13 @@ package ca.bc.gov.restapi.results.postgres.service; +import ca.bc.gov.restapi.results.common.util.TimestampUtil; import ca.bc.gov.restapi.results.postgres.dto.FreeGrowingMilestonesDto; 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.dto.DashboardFiltesDto; import ca.bc.gov.restapi.results.postgres.entity.OpeningsLastYearEntity; import ca.bc.gov.restapi.results.postgres.repository.OpeningsLastYearRepository; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.time.Month; import java.util.ArrayList; import java.util.HashMap; @@ -28,10 +31,10 @@ public class DashboardMetricsService { /** * Get openings submission trends data for the opening per year chart. * - * @param filters Possible filter, see {@link OpeningsPerYearFiltersDto} for more. + * @param filters Possible filter, see {@link DashboardFiltesDto} for more. * @return A list of {@link OpeningsPerYearDto} for the opening chart. */ - public List getOpeningsSubmissionTrends(OpeningsPerYearFiltersDto filters) { + public List getOpeningsSubmissionTrends(DashboardFiltesDto filters) { log.info("Getting Opening Submission Trends with filters {}", filters.toString()); List entities = @@ -61,6 +64,7 @@ public List getOpeningsSubmissionTrends(OpeningsPerYearFilte } } + // Iterate over the found records filtering and putting them into the right month for (OpeningsLastYearEntity entity : entities) { // Org Unit filter - District if (!Objects.isNull(filters.orgUnit()) @@ -92,7 +96,7 @@ public List getOpeningsSubmissionTrends(OpeningsPerYearFilte for (Integer monthKey : resultMap.keySet()) { List monthDataList = resultMap.get(monthKey); String monthName = monthNamesMap.get(monthKey); - log.info("Month: {}", monthName); + log.info("Value {} for the month: {}", monthDataList.size(), monthName); chartData.add(new OpeningsPerYearDto(monthKey, monthName, monthDataList.size())); } @@ -102,13 +106,81 @@ public List getOpeningsSubmissionTrends(OpeningsPerYearFilte /** * Get free growing milestone declarations data for the chart. * - * @param filtersDto Possible filter, see {@link OpeningsPerYearFiltersDto} for more. + * @param filters Possible filter, see {@link DashboardFiltesDto} for more. * @return A list of {@link FreeGrowingMilestonesDto} for the chart. */ public List getFreeGrowingMilestoneChartData( - OpeningsPerYearFiltersDto filtersDto) { - // keep going from here - // rename OpeningsPerYearFiltersDto record to a generic name - return List.of(); + DashboardFiltesDto filters) { + log.info("Getting Free growing milestones with filters {}", filters.toString()); + + List entities = + openingsLastYearRepository.findAll(Sort.by("entryTimestamp").ascending()); + + if (entities.isEmpty()) { + log.info("No Free growing milestones data found!"); + return List.of(); + } + + Map> resultMap = new LinkedHashMap<>(); + + // Fill with all four pieces + for (int i = 0; i < 4; i++) { + resultMap.put(i, new ArrayList<>()); + } + + Map labelsMap = new HashMap<>(); + labelsMap.put(0, "0 - 5 months"); + labelsMap.put(1, "6 - 11 months"); + labelsMap.put(2, "12 - 17 months"); + labelsMap.put(3, "18 months"); + + int totalRecordsFiltered = 0; + + // Iterate over the found records filtering and putting them into the right piece + for (OpeningsLastYearEntity entity : entities) { + // Org Unit filter - District + if (!Objects.isNull(filters.orgUnit()) + && !entity.getOrgUnitCode().equals(filters.orgUnit())) { + continue; + } + + // Client number + if (!Objects.isNull(filters.clientNumber()) + && !entity.getClientNumber().equals(filters.clientNumber())) { + continue; + } + + // Entry start date filter + if (!Objects.isNull(filters.entryDateStart()) + && entity.getEntryTimestamp().isBefore(filters.entryDateStart())) { + continue; + } + + // Entry end date filter + if (!Objects.isNull(filters.entryDateEnd()) + && entity.getEntryTimestamp().isAfter(filters.entryDateEnd())) { + continue; + } + + int index = TimestampUtil.getLocalDateTimeIndex(entity.getEntryTimestamp()); + resultMap.get(index).add(entity); + totalRecordsFiltered++; + } + + List chartData = new ArrayList<>(); + for (Integer index : resultMap.keySet()) { + List groupList = resultMap.get(index); + String label = labelsMap.get(index); + log.info("{} openings in {} for label {}", groupList.size(), totalRecordsFiltered, label); + BigDecimal percentage = + new BigDecimal(String.valueOf(groupList.size())) + .divide( + new BigDecimal(String.valueOf(totalRecordsFiltered)), 10, RoundingMode.HALF_UP); + + log.info("Percentage {}% for the label: {}", percentage, label); + chartData.add(new FreeGrowingMilestonesDto(index, label, percentage)); + } + + return chartData; } } diff --git a/backend/src/main/resources/db/migration/V5__add_client_number_metrics_table.sql b/backend/src/main/resources/db/migration/V5__add_client_number_metrics_table.sql new file mode 100644 index 00000000..bbcf3d2e --- /dev/null +++ b/backend/src/main/resources/db/migration/V5__add_client_number_metrics_table.sql @@ -0,0 +1,2 @@ +alter table silva.openings_last_year + add column client_number VARCHAR(8) NOT NULL; diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpointTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpointTest.java index fc7abb12..ae2a7b5f 100644 --- a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpointTest.java +++ b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpointTest.java @@ -7,8 +7,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import ca.bc.gov.restapi.results.postgres.dto.DashboardFiltesDto; 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 java.util.List; import org.junit.jupiter.api.DisplayName; @@ -31,8 +31,7 @@ class DashboardMetricsEndpointTest { @Test @DisplayName("Opening submission trends with no filters should succeed") void getOpeningsSubmissionTrends_noFilters_shouldSucceed() throws Exception { - OpeningsPerYearFiltersDto filtersDto = - new OpeningsPerYearFiltersDto(null, null, null, null, null); + DashboardFiltesDto filtersDto = new DashboardFiltesDto(null, null, null, null, null); OpeningsPerYearDto dto = new OpeningsPerYearDto(1, "Jan", 70); when(dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto)).thenReturn(List.of(dto)); @@ -54,8 +53,7 @@ void getOpeningsSubmissionTrends_noFilters_shouldSucceed() throws Exception { @Test @DisplayName("Opening submission trends with no data should succeed") void getOpeningsSubmissionTrends_orgUnitFilter_shouldSucceed() throws Exception { - OpeningsPerYearFiltersDto filtersDto = - new OpeningsPerYearFiltersDto("DCR", null, null, null, null); + DashboardFiltesDto filtersDto = new DashboardFiltesDto("DCR", null, null, null, null); when(dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto)).thenReturn(List.of()); diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java index 2d2c979e..3dfd3af0 100644 --- a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java +++ b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java @@ -2,8 +2,8 @@ import static org.mockito.Mockito.when; +import ca.bc.gov.restapi.results.postgres.dto.DashboardFiltesDto; 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.entity.OpeningsLastYearEntity; import ca.bc.gov.restapi.results.postgres.repository.OpeningsLastYearRepository; import java.time.LocalDateTime; @@ -51,8 +51,7 @@ void getOpeningsSubmissionTrends_noFilters_shouldSucceed() throws Exception { List entities = mockOpeningsEntityList(); when(openingsLastYearRepository.findAll(SORT)).thenReturn(entities); - OpeningsPerYearFiltersDto filtersDto = - new OpeningsPerYearFiltersDto(null, null, null, null, null); + DashboardFiltesDto filtersDto = new DashboardFiltesDto(null, null, null, null, null); List list = dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto); String monthName = now.getMonth().name().toLowerCase(); @@ -72,8 +71,7 @@ void getOpeningsSubmissionTrends_orgUnitFilter_shouldSucceed() throws Exception List entities = mockOpeningsEntityList(); when(openingsLastYearRepository.findAll(SORT)).thenReturn(entities); - OpeningsPerYearFiltersDto filtersDto = - new OpeningsPerYearFiltersDto("AAA", null, null, null, null); + DashboardFiltesDto filtersDto = new DashboardFiltesDto("AAA", null, null, null, null); List list = dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto); String monthName = now.getMonth().name().toLowerCase(); @@ -93,8 +91,7 @@ void getOpeningsSubmissionTrends_statusFilter_shouldSucceed() { List entities = mockOpeningsEntityList(); when(openingsLastYearRepository.findAll(SORT)).thenReturn(entities); - OpeningsPerYearFiltersDto filtersDto = - new OpeningsPerYearFiltersDto(null, "APP", null, null, null); + DashboardFiltesDto filtersDto = new DashboardFiltesDto(null, "APP", null, null, null); List list = dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto); String monthName = now.getMonth().name().toLowerCase(); @@ -117,8 +114,8 @@ void getOpeningsSubmissionTrends_datesFilter_shouldSucceed() { LocalDateTime oneMonthBefore = now.minusMonths(1L); LocalDateTime oneMonthLater = now.plusMonths(1L); - OpeningsPerYearFiltersDto filtersDto = - new OpeningsPerYearFiltersDto(null, null, oneMonthBefore, oneMonthLater, null); + DashboardFiltesDto filtersDto = + new DashboardFiltesDto(null, null, oneMonthBefore, oneMonthLater, null); List list = dashboardMetricsService.getOpeningsSubmissionTrends(filtersDto); String monthName = now.getMonth().name().toLowerCase(); From b36b3fee71a7f17cdb27de3d09dbe3a979754667 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Tue, 9 Apr 2024 15:07:14 -0300 Subject: [PATCH 13/19] feat: my recent actions table data - requests tab jira issue silva-358 --- backend/pom.xml | 5 ++ .../results/common/util/TimestampUtil.java | 10 +-- .../postgres/config/PostgresJpaConfig.java | 2 +- .../dto/FreeGrowingMilestonesDto.java | 3 +- .../dto/MyRecentActionsRequestsDto.java | 34 ++++++++ .../postgres/dto/UserOpeningCreateDto.java | 8 +- .../endpoint/DashboardMetricsEndpoint.java | 42 +++++++++- .../entity/OpeningsActivityEntity.java | 39 +++++++++ .../entity/OpeningsLastYearEntity.java | 8 +- .../OpeningsActivityRepository.java | 12 +++ .../service/DashboardMetricsService.java | 79 +++++++++++++++++-- .../V5__add_client_number_metrics_table.sql | 15 ++++ .../service/DashboardMetricsServiceTest.java | 10 ++- 13 files changed, 245 insertions(+), 22 deletions(-) create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/MyRecentActionsRequestsDto.java create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/OpeningsActivityEntity.java create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsActivityRepository.java diff --git a/backend/pom.xml b/backend/pom.xml index e8c50967..805d78c8 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -129,6 +129,11 @@ 1.18.30 true + + org.ocpsoft.prettytime + prettytime + 5.0.7.Final + diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/common/util/TimestampUtil.java b/backend/src/main/java/ca/bc/gov/restapi/results/common/util/TimestampUtil.java index f889d243..33398a66 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/common/util/TimestampUtil.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/common/util/TimestampUtil.java @@ -29,7 +29,7 @@ public static LocalDateTime parseDateString(String dateStr) { * 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 + * @return An integer representing the index */ public static int getLocalDateTimeIndex(LocalDateTime entryLocalDateTime) { // index 0 -> 0 to 5 months @@ -40,12 +40,12 @@ public static int getLocalDateTimeIndex(LocalDateTime entryLocalDateTime) { LocalDate now = LocalDate.now(); Period diff = Period.between(entryLocalDate, now); - int months = diff.getMonths(); - if (months <= 5) { + int totalMonths = diff.getMonths() + (diff.getYears() * 12); + if (totalMonths <= 5) { return 0; - } else if (months <= 11) { + } else if (totalMonths <= 11) { return 1; - } else if (months <= 17) { + } else if (totalMonths <= 17) { return 2; } else { return 3; diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/config/PostgresJpaConfig.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/config/PostgresJpaConfig.java index 1065f5fd..2ece5ac0 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/config/PostgresJpaConfig.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/config/PostgresJpaConfig.java @@ -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 = diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/FreeGrowingMilestonesDto.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/FreeGrowingMilestonesDto.java index 0e24ef82..d41ea9bf 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/FreeGrowingMilestonesDto.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/FreeGrowingMilestonesDto.java @@ -3,4 +3,5 @@ import java.math.BigDecimal; /** This record represent a slice of the free growing milestone chart. */ -public record FreeGrowingMilestonesDto(Integer index, String label, BigDecimal percentage) {} +public record FreeGrowingMilestonesDto( + Integer index, String label, Integer amount, BigDecimal percentage) {} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/MyRecentActionsRequestsDto.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/MyRecentActionsRequestsDto.java new file mode 100644 index 00000000..fa317ae8 --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/MyRecentActionsRequestsDto.java @@ -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) {} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/UserOpeningCreateDto.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/UserOpeningCreateDto.java index 32150a96..2aef8efc 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/UserOpeningCreateDto.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/UserOpeningCreateDto.java @@ -1,4 +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(String openingId) {} +public record UserOpeningCreateDto( + @Schema( + description = "System generated value uniquely identifying the opening.", + example = "1541297") + String openingId) {} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java index 28f59e49..2b886761 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java @@ -3,6 +3,7 @@ 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.service.DashboardMetricsService; import io.swagger.v3.oas.annotations.Operation; @@ -24,7 +25,7 @@ @RestController @RequestMapping("/api/dashboard-metrics") @Tag( - name = "Dashboard Metrics", + name = "Dashboard Metrics (SILVA)", description = "Endpoints fot the Dashboard metrics charts in the `SILVA` schema") @RequiredArgsConstructor public class DashboardMetricsEndpoint { @@ -48,7 +49,8 @@ public class DashboardMetricsEndpoint { 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 on the table. No response body."), @@ -124,7 +126,8 @@ public ResponseEntity> getOpeningsSubmissionTrends( responses = { @ApiResponse( responseCode = "200", - description = "An array with four objects, one for each piece of the chart."), + 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."), @@ -182,4 +185,37 @@ public ResponseEntity> getFreeGrowingMilestonesDa return ResponseEntity.ok(milestonesDto); } + + /** + * 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> getUserRecentOpeningsActions() { + List actionsDto = + dashboardMetricsService.getUserRecentOpeningsActions(); + + if (actionsDto.isEmpty()) { + return ResponseEntity.noContent().build(); + } + + return ResponseEntity.ok(actionsDto); + } } diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/OpeningsActivityEntity.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/OpeningsActivityEntity.java new file mode 100644 index 00000000..d33a8878 --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/OpeningsActivityEntity.java @@ -0,0 +1,39 @@ +package ca.bc.gov.restapi.results.postgres.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.time.LocalDateTime; +import lombok.Getter; +import lombok.Setter; + +/** This class represents a record in the database for the openings_activity table. */ +@Getter +@Setter +@Entity +@Table(name = "openings_activity") +public class OpeningsActivityEntity { + + @Id + @Column(name = "opening_id") + private Long openingId; + + @Column(name = "activity_type_code", length = 3) + private String activityTypeCode; + + @Column(name = "activity_type_desc", length = 120) + private String activityTypeDesc; + + @Column(name = "status_code", length = 3, nullable = false) + private String statusCode; + + @Column(name = "status_desc", length = 120, nullable = false) + private String statusDesc; + + @Column(name = "last_updated", nullable = false) + private LocalDateTime lastUpdated; + + @Column(name = "entry_userid", length = 30, nullable = false) + private String entryUserid; +} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/OpeningsLastYearEntity.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/OpeningsLastYearEntity.java index 061bc01e..d28950aa 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/OpeningsLastYearEntity.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/OpeningsLastYearEntity.java @@ -19,7 +19,7 @@ public class OpeningsLastYearEntity { @Column(name = "opening_id") private String openingId; - @Column(name = "opening_entry_userid", nullable = false) + @Column(name = "opening_entry_userid", nullable = false, length = 30) private String userId; @Column(name = "entry_timestamp", nullable = false) @@ -28,12 +28,12 @@ public class OpeningsLastYearEntity { @Column(name = "update_timestamp", nullable = false) private LocalDateTime updateTimestamp; - @Column(name = "status_code", nullable = false) + @Column(name = "status_code", nullable = false, length = 3) private String status; - @Column(name = "org_unit_code", nullable = false) + @Column(name = "org_unit_code", nullable = false, length = 6) private String orgUnitCode; - @Column(name = "client_number", nullable = false) + @Column(name = "client_number", nullable = false, length = 8) private String clientNumber; } diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsActivityRepository.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsActivityRepository.java new file mode 100644 index 00000000..c173c972 --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsActivityRepository.java @@ -0,0 +1,12 @@ +package ca.bc.gov.restapi.results.postgres.repository; + +import ca.bc.gov.restapi.results.postgres.entity.OpeningsActivityEntity; +import java.util.List; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.repository.JpaRepository; + +/** This interface provides access to the database for the OpeningsActivityEntity entity. */ +public interface OpeningsActivityRepository extends JpaRepository { + + List findAllByEntryUserid(String userId, Sort sort); +} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsService.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsService.java index 3a0e2e0b..f5edd019 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsService.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsService.java @@ -1,10 +1,14 @@ package ca.bc.gov.restapi.results.postgres.service; +import ca.bc.gov.restapi.results.common.security.LoggedUserService; 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.DashboardFiltesDto; +import ca.bc.gov.restapi.results.postgres.entity.OpeningsActivityEntity; import ca.bc.gov.restapi.results.postgres.entity.OpeningsLastYearEntity; +import ca.bc.gov.restapi.results.postgres.repository.OpeningsActivityRepository; import ca.bc.gov.restapi.results.postgres.repository.OpeningsLastYearRepository; import java.math.BigDecimal; import java.math.RoundingMode; @@ -17,6 +21,7 @@ import java.util.Objects; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.ocpsoft.prettytime.PrettyTime; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; @@ -28,6 +33,10 @@ public class DashboardMetricsService { private final OpeningsLastYearRepository openingsLastYearRepository; + private final OpeningsActivityRepository openingsActivityRepository; + + private final LoggedUserService loggedUserService; + /** * Get openings submission trends data for the opening per year chart. * @@ -114,7 +123,7 @@ public List getFreeGrowingMilestoneChartData( log.info("Getting Free growing milestones with filters {}", filters.toString()); List entities = - openingsLastYearRepository.findAll(Sort.by("entryTimestamp").ascending()); + openingsLastYearRepository.findAll(Sort.by("openingId").ascending()); if (entities.isEmpty()) { log.info("No Free growing milestones data found!"); @@ -168,17 +177,75 @@ public List getFreeGrowingMilestoneChartData( } List chartData = new ArrayList<>(); + BigDecimal hundred = new BigDecimal("100"); + BigDecimal hundredSum = new BigDecimal("100"); for (Integer index : resultMap.keySet()) { List groupList = resultMap.get(index); String label = labelsMap.get(index); - log.info("{} openings in {} for label {}", groupList.size(), totalRecordsFiltered, label); + int value = groupList.size(); + log.info("{} openings of {} for label '{}'", value, totalRecordsFiltered, label); BigDecimal percentage = - new BigDecimal(String.valueOf(groupList.size())) + new BigDecimal(String.valueOf(value)) .divide( - new BigDecimal(String.valueOf(totalRecordsFiltered)), 10, RoundingMode.HALF_UP); + new BigDecimal(String.valueOf(totalRecordsFiltered)), 10, RoundingMode.HALF_UP) + .setScale(2, RoundingMode.HALF_UP) + .multiply(hundred); + + if (index < 3) { + hundredSum = hundredSum.subtract(percentage); + } else { + percentage = hundredSum; + } log.info("Percentage {}% for the label: {}", percentage, label); - chartData.add(new FreeGrowingMilestonesDto(index, label, percentage)); + chartData.add(new FreeGrowingMilestonesDto(index, label, value, percentage)); + } + + return chartData; + } + + /** + * Get My recent actions table data for the Request tabs. + * + * @return A list of {@link MyRecentActionsRequestsDto} with 5 rows. + */ + public List getUserRecentOpeningsActions() { + log.info("Getting the last 5 openings activities for the requests tab"); + + String userId = loggedUserService.getLoggedUserId(); + + Sort sort = Sort.by("lastUpdated").descending(); + List openingList = + openingsActivityRepository.findAllByEntryUserid(userId, sort); + + if (openingList.isEmpty()) { + log.info("No recent activities data found for the user!"); + return List.of(); + } + + PrettyTime prettyTime = new PrettyTime(); + + List chartData = new ArrayList<>(); + for (OpeningsActivityEntity entity : openingList) { + String statusDesc = entity.getActivityTypeDesc(); + if (Objects.isNull(statusDesc)) { + statusDesc = "Created"; + } + + MyRecentActionsRequestsDto dto = + new MyRecentActionsRequestsDto( + statusDesc, + entity.getOpeningId(), + entity.getStatusCode(), + entity.getStatusDesc(), + prettyTime.format(entity.getLastUpdated()), + entity.getLastUpdated()); + + chartData.add(dto); + + if (chartData.size() == 5) { + break; + } } return chartData; diff --git a/backend/src/main/resources/db/migration/V5__add_client_number_metrics_table.sql b/backend/src/main/resources/db/migration/V5__add_client_number_metrics_table.sql index bbcf3d2e..0e87629a 100644 --- a/backend/src/main/resources/db/migration/V5__add_client_number_metrics_table.sql +++ b/backend/src/main/resources/db/migration/V5__add_client_number_metrics_table.sql @@ -1,2 +1,17 @@ alter table silva.openings_last_year add column client_number VARCHAR(8) NOT NULL; + +-- drop table and recreate fixing the table name and columns +drop table silva.openings_activitiy; + +create table IF NOT EXISTS silva.openings_activity ( + opening_id DECIMAL(10,0) NOT NULL, + activity_type_code VARCHAR(3), + activity_type_desc VARCHAR(120), + status_code VARCHAR(3) NOT NULL, + status_desc VARCHAR(120) NOT NULL, + last_updated TIMESTAMP NOT NULL, + entry_userid VARCHAR(30) NOT NULL, + constraint openings_activitiy_pk + primary key(opening_id) +); diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java index 3dfd3af0..cc49ffe1 100644 --- a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java +++ b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java @@ -2,9 +2,11 @@ import static org.mockito.Mockito.when; +import ca.bc.gov.restapi.results.common.security.LoggedUserService; import ca.bc.gov.restapi.results.postgres.dto.DashboardFiltesDto; import ca.bc.gov.restapi.results.postgres.dto.OpeningsPerYearDto; import ca.bc.gov.restapi.results.postgres.entity.OpeningsLastYearEntity; +import ca.bc.gov.restapi.results.postgres.repository.OpeningsActivityRepository; import ca.bc.gov.restapi.results.postgres.repository.OpeningsLastYearRepository; import java.time.LocalDateTime; import java.util.List; @@ -22,6 +24,10 @@ class DashboardMetricsServiceTest { @Mock OpeningsLastYearRepository openingsLastYearRepository; + @Mock OpeningsActivityRepository openingsActivityRepository; + + @Mock LoggedUserService loggedUserService; + private DashboardMetricsService dashboardMetricsService; private static final Sort SORT = Sort.by("entryTimestamp").ascending(); @@ -41,7 +47,9 @@ private List mockOpeningsEntityList() { @BeforeEach void setup() { - dashboardMetricsService = new DashboardMetricsService(openingsLastYearRepository); + dashboardMetricsService = + new DashboardMetricsService( + openingsLastYearRepository, openingsActivityRepository, loggedUserService); } @Test From 0b58f91f8a82dd124bd21cdb813472c2d12ed694 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Tue, 9 Apr 2024 15:20:40 -0300 Subject: [PATCH 14/19] chore: change opening data type from text to number --- .../results/postgres/dto/UserOpeningCreateDto.java | 2 +- .../postgres/entity/OpeningsLastYearEntity.java | 2 +- .../results/postgres/entity/UserOpeningEntity.java | 2 +- .../results/postgres/entity/UserOpeningEntityId.java | 2 +- .../repository/OpeningsLastYearRepository.java | 4 ++-- .../results/postgres/service/UserOpeningService.java | 2 +- .../OpeningsLastYearRepositoryIntegrationTest.java | 4 ++-- .../postgres/service/DashboardMetricsServiceTest.java | 2 +- .../OpeningsLastYearRepositoryIntegrationTest.sql | 11 ++++++----- 9 files changed, 16 insertions(+), 15 deletions(-) diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/UserOpeningCreateDto.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/UserOpeningCreateDto.java index 2aef8efc..bdfbce93 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/UserOpeningCreateDto.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/UserOpeningCreateDto.java @@ -7,4 +7,4 @@ public record UserOpeningCreateDto( @Schema( description = "System generated value uniquely identifying the opening.", example = "1541297") - String openingId) {} + Long openingId) {} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/OpeningsLastYearEntity.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/OpeningsLastYearEntity.java index d28950aa..60360441 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/OpeningsLastYearEntity.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/OpeningsLastYearEntity.java @@ -17,7 +17,7 @@ public class OpeningsLastYearEntity { @Id @Column(name = "opening_id") - private String openingId; + private Long openingId; @Column(name = "opening_entry_userid", nullable = false, length = 30) private String userId; diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/UserOpeningEntity.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/UserOpeningEntity.java index ad10a717..2ab815fa 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/UserOpeningEntity.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/UserOpeningEntity.java @@ -26,5 +26,5 @@ public class UserOpeningEntity { @Id @Column(name = "opening_id") - private String openingId; + private Long openingId; } diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/UserOpeningEntityId.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/UserOpeningEntityId.java index 343493c7..e7af80be 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/UserOpeningEntityId.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/entity/UserOpeningEntityId.java @@ -19,5 +19,5 @@ public class UserOpeningEntityId { @NonNull private String userId; - @NonNull private String openingId; + @NonNull private Long openingId; } diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepository.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepository.java index f4fc8de7..ce7d34cd 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepository.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepository.java @@ -6,8 +6,8 @@ import org.springframework.data.jpa.repository.Query; /** This interface provides access to the database for the OpeningsLastYearEntity entity. */ -public interface OpeningsLastYearRepository extends JpaRepository { +public interface OpeningsLastYearRepository extends JpaRepository { @Query("from OpeningsLastYearEntity o where o.openingId in ?1") - List findAllByOpeningIdInList(List openingIdList); + List findAllByOpeningIdInList(List openingIdList); } diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningService.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningService.java index d81201f0..04bf697b 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningService.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningService.java @@ -47,7 +47,7 @@ public void saveOpeningsToUser(List createDtos) { log.info("{} Opening IDs to save in the favourites!", createDtos.size()); // validate all openings - List openingIdList = createDtos.stream().map(UserOpeningCreateDto::openingId).toList(); + List openingIdList = createDtos.stream().map(UserOpeningCreateDto::openingId).toList(); log.info("Looking for Openings in the spar.openings_last_year table!"); List entities = diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepositoryIntegrationTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepositoryIntegrationTest.java index ee60eefe..7e5fcfdc 100644 --- a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepositoryIntegrationTest.java +++ b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsLastYearRepositoryIntegrationTest.java @@ -21,7 +21,7 @@ class OpeningsLastYearRepositoryIntegrationTest { @Test @DisplayName("find all by Opening ID in List") void findAllByOpeningIdInListTest() { - List idList = List.of("7012511", "7012512", "7012513"); + List idList = List.of(7012511L, 7012512L, 7012513L); List openingList = openingsLastYearRepository.findAllByOpeningIdInList(idList); @@ -30,7 +30,7 @@ void findAllByOpeningIdInListTest() { OpeningsLastYearEntity first = openingList.get(0); - Assertions.assertEquals("7012511", first.getOpeningId()); + Assertions.assertEquals(7012511L, first.getOpeningId()); Assertions.assertEquals("TEST", first.getUserId()); Assertions.assertEquals("APP", first.getStatus()); Assertions.assertEquals("DCR", first.getOrgUnitCode()); diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java index cc49ffe1..db701865 100644 --- a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java +++ b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java @@ -36,7 +36,7 @@ private List mockOpeningsEntityList() { LocalDateTime entryTimestamp = LocalDateTime.now(); OpeningsLastYearEntity entity = new OpeningsLastYearEntity(); - entity.setOpeningId("123456"); + entity.setOpeningId(123456L); entity.setUserId("userId"); entity.setEntryTimestamp(entryTimestamp); entity.setUpdateTimestamp(entryTimestamp); diff --git a/backend/src/test/resources/sql_scripts/OpeningsLastYearRepositoryIntegrationTest.sql b/backend/src/test/resources/sql_scripts/OpeningsLastYearRepositoryIntegrationTest.sql index 99d82469..23ddd360 100644 --- a/backend/src/test/resources/sql_scripts/OpeningsLastYearRepositoryIntegrationTest.sql +++ b/backend/src/test/resources/sql_scripts/OpeningsLastYearRepositoryIntegrationTest.sql @@ -5,9 +5,10 @@ insert into openings_last_year ( ,update_timestamp ,status_code ,org_unit_code + ,client_number ) values - ('7012511', 'TEST', '2024-04-05', '2024-04-08', 'APP', 'DCR'), - ('7012512', 'TEST', '2024-02-03', '2024-03-08', 'APP', 'DCR'), - ('7012513', 'TEST', '2024-03-04', '2024-04-08', 'APP', 'DCR'), - ('7012514', 'TEST', '2024-01-15', '2024-02-15', 'APP', 'DCR'), - ('7012515', 'TEST', '2024-02-18', '2024-02-25', 'APP', 'DCR'); + (7012511, 'TEST', '2024-04-05', '2024-04-08', 'APP', 'DCR', '00012797'), + (7012512, 'TEST', '2024-02-03', '2024-03-08', 'APP', 'DCR', '00012797'), + (7012513, 'TEST', '2024-03-04', '2024-04-08', 'APP', 'DCR', '00012797'), + (7012514, 'TEST', '2024-01-15', '2024-02-15', 'APP', 'DCR', '00012797'), + (7012515, 'TEST', '2024-02-18', '2024-02-25', 'APP', 'DCR', '00012797'); From 37101c2717c40e11b1852a9c6385c91ee38d6eef Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Tue, 9 Apr 2024 15:41:01 -0300 Subject: [PATCH 15/19] test: add unit tests for the timestamp util class --- .../results/common/util/TimestampUtil.java | 2 + .../common/util/TimestampUtilTest.java | 46 +++++++++++++++++++ .../enums/OpeningCategoryEnumTest.java | 3 +- .../enums/OpeningStatusEnumTest.java | 3 +- 4 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 backend/src/test/java/ca/bc/gov/restapi/results/common/util/TimestampUtilTest.java rename backend/src/test/java/ca/bc/gov/restapi/results/{ => oracle}/enums/OpeningCategoryEnumTest.java (83%) rename backend/src/test/java/ca/bc/gov/restapi/results/{ => oracle}/enums/OpeningStatusEnumTest.java (83%) diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/common/util/TimestampUtil.java b/backend/src/main/java/ca/bc/gov/restapi/results/common/util/TimestampUtil.java index 33398a66..a79a0528 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/common/util/TimestampUtil.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/common/util/TimestampUtil.java @@ -9,6 +9,8 @@ /** This class contains useful methods for parsing and handling timestamps. */ public class TimestampUtil { + private TimestampUtil() {} + /** * Parses a date string to a {@link LocalDateTime} instance. Format: yyyy-MM-dd. * diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/common/util/TimestampUtilTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/common/util/TimestampUtilTest.java new file mode 100644 index 00000000..2b290b4e --- /dev/null +++ b/backend/src/test/java/ca/bc/gov/restapi/results/common/util/TimestampUtilTest.java @@ -0,0 +1,46 @@ +package ca.bc.gov.restapi.results.common.util; + +import java.time.LocalDateTime; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class TimestampUtilTest { + + @Test + void parseDateStringTest() { + LocalDateTime dateTimeParsed = TimestampUtil.parseDateString("2024-04-09"); + Assertions.assertNotNull(dateTimeParsed); + Assertions.assertEquals(2024, dateTimeParsed.getYear()); + Assertions.assertEquals(4, dateTimeParsed.getMonthValue()); + Assertions.assertEquals(9, dateTimeParsed.getDayOfMonth()); + + LocalDateTime dateTimeNull = TimestampUtil.parseDateString(null); + Assertions.assertNull(dateTimeNull); + } + + @Test + void getLocalDateTimeIndexTest() { + LocalDateTime now = LocalDateTime.now(); + + Assertions.assertEquals(0, TimestampUtil.getLocalDateTimeIndex(now.minusMonths(1L))); + Assertions.assertEquals(0, TimestampUtil.getLocalDateTimeIndex(now.minusMonths(2L))); + Assertions.assertEquals(0, TimestampUtil.getLocalDateTimeIndex(now.minusMonths(3L))); + Assertions.assertEquals(0, TimestampUtil.getLocalDateTimeIndex(now.minusMonths(4L))); + Assertions.assertEquals(0, TimestampUtil.getLocalDateTimeIndex(now.minusMonths(5L))); + Assertions.assertEquals(1, TimestampUtil.getLocalDateTimeIndex(now.minusMonths(6L))); + Assertions.assertEquals(1, TimestampUtil.getLocalDateTimeIndex(now.minusMonths(7L))); + Assertions.assertEquals(1, TimestampUtil.getLocalDateTimeIndex(now.minusMonths(8L))); + Assertions.assertEquals(1, TimestampUtil.getLocalDateTimeIndex(now.minusMonths(9L))); + Assertions.assertEquals(1, TimestampUtil.getLocalDateTimeIndex(now.minusMonths(10L))); + Assertions.assertEquals(1, TimestampUtil.getLocalDateTimeIndex(now.minusMonths(11L))); + Assertions.assertEquals(2, TimestampUtil.getLocalDateTimeIndex(now.minusMonths(12L))); + Assertions.assertEquals(2, TimestampUtil.getLocalDateTimeIndex(now.minusMonths(13L))); + Assertions.assertEquals(2, TimestampUtil.getLocalDateTimeIndex(now.minusMonths(14L))); + Assertions.assertEquals(2, TimestampUtil.getLocalDateTimeIndex(now.minusMonths(15L))); + Assertions.assertEquals(2, TimestampUtil.getLocalDateTimeIndex(now.minusMonths(16L))); + Assertions.assertEquals(2, TimestampUtil.getLocalDateTimeIndex(now.minusMonths(17L))); + Assertions.assertEquals(3, TimestampUtil.getLocalDateTimeIndex(now.minusMonths(18L))); + Assertions.assertEquals(3, TimestampUtil.getLocalDateTimeIndex(now.minusMonths(36L))); + } +} diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/enums/OpeningCategoryEnumTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/oracle/enums/OpeningCategoryEnumTest.java similarity index 83% rename from backend/src/test/java/ca/bc/gov/restapi/results/enums/OpeningCategoryEnumTest.java rename to backend/src/test/java/ca/bc/gov/restapi/results/oracle/enums/OpeningCategoryEnumTest.java index 029c0f9d..b725e5e5 100644 --- a/backend/src/test/java/ca/bc/gov/restapi/results/enums/OpeningCategoryEnumTest.java +++ b/backend/src/test/java/ca/bc/gov/restapi/results/oracle/enums/OpeningCategoryEnumTest.java @@ -1,6 +1,5 @@ -package ca.bc.gov.restapi.results.enums; +package ca.bc.gov.restapi.results.oracle.enums; -import ca.bc.gov.restapi.results.oracle.enums.OpeningCategoryEnum; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/enums/OpeningStatusEnumTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/oracle/enums/OpeningStatusEnumTest.java similarity index 83% rename from backend/src/test/java/ca/bc/gov/restapi/results/enums/OpeningStatusEnumTest.java rename to backend/src/test/java/ca/bc/gov/restapi/results/oracle/enums/OpeningStatusEnumTest.java index 4a48a358..ba0a8ca5 100644 --- a/backend/src/test/java/ca/bc/gov/restapi/results/enums/OpeningStatusEnumTest.java +++ b/backend/src/test/java/ca/bc/gov/restapi/results/oracle/enums/OpeningStatusEnumTest.java @@ -1,6 +1,5 @@ -package ca.bc.gov.restapi.results.enums; +package ca.bc.gov.restapi.results.oracle.enums; -import ca.bc.gov.restapi.results.oracle.enums.OpeningStatusEnum; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; From 09e6b06d3851289fb6b9cc9c05f9c4a92d63b958 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Tue, 9 Apr 2024 17:40:57 -0300 Subject: [PATCH 16/19] test: add user opening endpoint unit tests --- .../results/oracle/entity/OpeningEntity.java | 2 - .../postgres/dto/UserOpeningCreateDto.java | 10 -- .../endpoint/UserOpeningEndpoint.java | 91 ++++++++----- .../postgres/service/UserOpeningService.java | 120 ++++++++++------- .../common/util/TimestampUtilTest.java | 1 - .../DashboardMetricsEndpointTest.java | 127 ++++++++++++++++++ .../endpoint/UserOpeningEndpointTest.java | 78 ++++++++++- 7 files changed, 328 insertions(+), 101 deletions(-) delete mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/UserOpeningCreateDto.java diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/oracle/entity/OpeningEntity.java b/backend/src/main/java/ca/bc/gov/restapi/results/oracle/entity/OpeningEntity.java index eac1ea3a..784078a7 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/oracle/entity/OpeningEntity.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/oracle/entity/OpeningEntity.java @@ -20,11 +20,9 @@ public class OpeningEntity { private Long id; @Column(name = "OPENING_STATUS_CODE", length = 3) - // private OpeningStatusEnum status; private String status; @Column(name = "OPEN_CATEGORY_CODE", length = 7) - // private OpeningCategoryEnum category; private String category; @Column(name = "ENTRY_USERID", length = 30) diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/UserOpeningCreateDto.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/UserOpeningCreateDto.java deleted file mode 100644 index bdfbce93..00000000 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/UserOpeningCreateDto.java +++ /dev/null @@ -1,10 +0,0 @@ -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") - Long openingId) {} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpoint.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpoint.java index 06c0aa7e..fc0523e9 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpoint.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpoint.java @@ -1,7 +1,6 @@ package ca.bc.gov.restapi.results.postgres.endpoint; -import ca.bc.gov.restapi.results.postgres.dto.UserOpeningCreateDto; -import ca.bc.gov.restapi.results.postgres.entity.UserOpeningEntity; +import ca.bc.gov.restapi.results.postgres.dto.MyRecentActionsRequestsDto; import ca.bc.gov.restapi.results.postgres.service.UserOpeningService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -13,19 +12,17 @@ import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** This class holds resources for exposing user openings saved as favourites. */ @RestController -@RequestMapping(path = "/api/user-openings", produces = MediaType.APPLICATION_JSON_VALUE) +@RequestMapping("/api/user-openings") @Tag( name = "User Openings (SILVA)", description = "Endpoints to handle user favourite Openings in the `SILVA` schema.") @@ -35,28 +32,29 @@ public class UserOpeningEndpoint { private final UserOpeningService userOpeningService; /** - * Gets all saved Openings to a user. + * Gets up to three tracked Openings to a user. * * @return A list of openings or the http code 204-No Content. */ - @GetMapping + @GetMapping("/dashboard-track-openings") @Operation( - summary = "Gets all saved Openings to a user", - description = "Gets all openings highlighted by the user, saved as favourite.", + summary = "Gets up to three updated Openings to a user", + description = "Gets up to three updated openings that got saved by the user.", responses = { @ApiResponse( responseCode = "200", - description = "An array containing one or more Openings found for the user."), + description = "An array containing up to three Openings for the user.", + content = @Content(mediaType = "application/json")), @ApiResponse( responseCode = "204", - description = "No data found in the dable. No response body."), + description = "No data found for this user. No response body."), @ApiResponse( responseCode = "401", description = "Access token is missing or invalid", content = @Content(schema = @Schema(implementation = Void.class))) }) - public ResponseEntity> getAll() { - List userOpenings = userOpeningService.getAllUserOpenings(); + public ResponseEntity> getUserTrackedOpenings() { + List userOpenings = userOpeningService.getUserTrackedOpenings(); if (userOpenings.isEmpty()) { return ResponseEntity.noContent().build(); } @@ -65,48 +63,67 @@ public ResponseEntity> getAll() { } /** - * Saves one or more Opening ID as favourite to an user. + * Saves one Opening ID as favourite to an user. * - * @param createDtos List of {@link UserOpeningCreateDto} with opening ids + * @param id The opening ID. * @return HTTP status code 201 if success, no response body. */ - @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) + @PostMapping("/{id}") @Operation( - summary = "Saves one or more Opening ID as favourite to an user", - description = "Allow users to save on or more Opening IDs at the same request as favourite.", + summary = "Saves one Opening ID as favourite to an user", + description = "Allow users to save Opening IDs to track them easily in the dashboard.", responses = { - @ApiResponse( - responseCode = "201", - description = "All opening ids were found and successfully saved to the user."), + @ApiResponse(responseCode = "201", description = "Opening successfully saved to the user."), @ApiResponse( responseCode = "401", description = "Access token is missing or invalid", - content = @Content(schema = @Schema(implementation = Void.class))), - @ApiResponse( - responseCode = "404", - description = "One or more openings were not found in the system.") + content = @Content(schema = @Schema(implementation = Void.class))) }) - public ResponseEntity saveUserOpening(@RequestBody List createDtos) { - userOpeningService.saveOpeningsToUser(createDtos); + public ResponseEntity saveUserOpening( + @Parameter( + name = "id", + in = ParameterIn.PATH, + description = "Opening ID", + required = true, + schema = @Schema(type = "integer", format = "int64")) + @PathVariable + Long id) { + userOpeningService.saveOpeningToUser(id); return ResponseEntity.status(HttpStatus.CREATED).build(); } /** * Deletes one or more user openings from their favourite list. * - * @param openingIds List with one or more Opening IDs. + * @param id The opening ID. * @return HTTP status code 204 if success, no response body. */ - @DeleteMapping + @DeleteMapping("/{id}") + @Operation( + summary = "Deleted an Opening ID from the user's favourite", + description = "Allow users to remove saved Opening IDs from their favourite list.", + responses = { + @ApiResponse( + responseCode = "200", + description = "Opening successfully removed for the user."), + @ApiResponse( + responseCode = "401", + description = "Access token is missing or invalid", + content = @Content(schema = @Schema(implementation = Void.class))), + @ApiResponse( + responseCode = "404", + description = "Opening not found in the user's favourite.") + }) public ResponseEntity deleteUserOpening( @Parameter( - name = "openingIds", - in = ParameterIn.QUERY, - description = "Opening ID list separated by comma", - required = true) - @RequestParam - List openingIds) { - userOpeningService.deleteOpeningsFromUserFavourite(openingIds); + name = "id", + in = ParameterIn.PATH, + description = "Opening ID", + required = true, + schema = @Schema(type = "integer", format = "int64")) + @PathVariable + Long id) { + userOpeningService.deleteOpeningFromUserFavourite(id); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } } diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningService.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningService.java index 04bf697b..5175b3e2 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningService.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningService.java @@ -1,18 +1,20 @@ package ca.bc.gov.restapi.results.postgres.service; -import ca.bc.gov.restapi.results.common.exception.OpeningNotFoundException; import ca.bc.gov.restapi.results.common.exception.UserOpeningNotFoundException; import ca.bc.gov.restapi.results.common.security.LoggedUserService; -import ca.bc.gov.restapi.results.postgres.dto.UserOpeningCreateDto; -import ca.bc.gov.restapi.results.postgres.entity.OpeningsLastYearEntity; +import ca.bc.gov.restapi.results.postgres.dto.MyRecentActionsRequestsDto; +import ca.bc.gov.restapi.results.postgres.entity.OpeningsActivityEntity; import ca.bc.gov.restapi.results.postgres.entity.UserOpeningEntity; -import ca.bc.gov.restapi.results.postgres.repository.OpeningsLastYearRepository; +import ca.bc.gov.restapi.results.postgres.entity.UserOpeningEntityId; +import ca.bc.gov.restapi.results.postgres.repository.OpeningsActivityRepository; import ca.bc.gov.restapi.results.postgres.repository.UserOpeningRepository; import jakarta.transaction.Transactional; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.ocpsoft.prettytime.PrettyTime; import org.springframework.stereotype.Service; /** This class contains methods for handling User favourite Openings. */ @@ -25,79 +27,97 @@ public class UserOpeningService { private final UserOpeningRepository userOpeningRepository; - private final OpeningsLastYearRepository openingsLastYearRepository; + private final OpeningsActivityRepository openingsActivityRepository; /** - * Gets all openings saved for an user. + * Gets user's tracked Openings. * - * @return A list of {@link UserOpeningEntity} containing the found records. + * @return A list of {@link MyRecentActionsRequestsDto} containing the found records. */ - public List getAllUserOpenings() { + public List getUserTrackedOpenings() { + log.info("Getting all user openings for the Track openings table"); + String userId = loggedUserService.getLoggedUserId(); - return userOpeningRepository.findAllByUserId(userId); + List userList = userOpeningRepository.findAllByUserId(userId); + + if (userList.isEmpty()) { + log.info("No saved openings for the current user!"); + return List.of(); + } + + List openingIds = userList.stream().map(UserOpeningEntity::getOpeningId).toList(); + List openingActivities = + openingsActivityRepository.findAllById(openingIds); + + if (openingActivities.isEmpty()) { + log.info("No records found on the opening activity table for the opening ID list!"); + return List.of(); + } + + List resultList = new ArrayList<>(); + + PrettyTime prettyTime = new PrettyTime(); + + for (OpeningsActivityEntity activityEntity : openingActivities) { + MyRecentActionsRequestsDto requestsDto = + new MyRecentActionsRequestsDto( + activityEntity.getActivityTypeDesc(), + activityEntity.getOpeningId(), + activityEntity.getStatusCode(), + activityEntity.getStatusDesc(), + prettyTime.format(activityEntity.getLastUpdated()), + activityEntity.getLastUpdated()); + + resultList.add(requestsDto); + + if (resultList.size() == 3) { + break; + } + } + + return resultList; } /** * Saves one or more Openings IDs to an user. * - * @param createDtos List with one ore more opening IDs. + * @param openingId The opening ID. */ @Transactional - public void saveOpeningsToUser(List createDtos) { - log.info("{} Opening IDs to save in the favourites!", createDtos.size()); - - // validate all openings - List openingIdList = createDtos.stream().map(UserOpeningCreateDto::openingId).toList(); - - log.info("Looking for Openings in the spar.openings_last_year table!"); - List entities = - openingsLastYearRepository.findAllByOpeningIdInList(openingIdList); - - if (entities.size() < createDtos.size()) { - log.info( - "Result list ({}) contains less records than Openings IDs received ({})!", - entities.size(), - createDtos.size()); - throw new OpeningNotFoundException(); - } + public void saveOpeningToUser(Long openingId) { + log.info("Opening ID to save in the user favourites: {}", openingId); final String userId = loggedUserService.getLoggedUserId(); - List entitiesToSave = new ArrayList<>(); - createDtos.forEach( - dto -> { - UserOpeningEntity entity = new UserOpeningEntity(); - entity.setUserId(userId); - entity.setOpeningId(dto.openingId()); - entitiesToSave.add(entity); - }); - - userOpeningRepository.saveAllAndFlush(entitiesToSave); - log.info("Opening IDs saved in the favourites!"); + UserOpeningEntity entity = new UserOpeningEntity(); + entity.setUserId(userId); + entity.setOpeningId(openingId); + + userOpeningRepository.saveAndFlush(entity); + log.info("Opening ID saved in the user's favourites!"); } /** * Deletes one or more user opening from favourite. * - * @param openingIds List with opening IDs to be deleted. + * @param openingId The opening ID. */ @Transactional - public void deleteOpeningsFromUserFavourite(List openingIds) { - log.info("{} Opening IDs to delete from the favourites!", openingIds.size()); + public void deleteOpeningFromUserFavourite(Long openingId) { + log.info("Opening ID to delete from the user's favourites: {}", openingId); String userId = loggedUserService.getLoggedUserId(); - List userOpenings = - userOpeningRepository.findAllByOpeningIdInAndUserId(openingIds, userId); + UserOpeningEntityId openingPk = new UserOpeningEntityId(userId, openingId); + + Optional userOpeningsOp = userOpeningRepository.findById(openingPk); - if (userOpenings.size() < openingIds.size()) { - log.info( - "UserOpeningEntity result list ({}) contains less records than IDs received ({})!", - userOpenings.size(), - openingIds.size()); + if (userOpeningsOp.isEmpty()) { + log.info("Opening id {} not found in the user's favourite list!", openingId); throw new UserOpeningNotFoundException(); } - userOpeningRepository.deleteAll(userOpenings); - log.info("Opening IDs deleted from the favourites!"); + userOpeningRepository.delete(userOpeningsOp.get()); + userOpeningRepository.flush(); + log.info("Opening ID deleted from the favourites!"); } } diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/common/util/TimestampUtilTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/common/util/TimestampUtilTest.java index 2b290b4e..c885e0b6 100644 --- a/backend/src/test/java/ca/bc/gov/restapi/results/common/util/TimestampUtilTest.java +++ b/backend/src/test/java/ca/bc/gov/restapi/results/common/util/TimestampUtilTest.java @@ -1,7 +1,6 @@ package ca.bc.gov.restapi.results.common.util; import java.time.LocalDateTime; - import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpointTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpointTest.java index ae2a7b5f..4ee94da3 100644 --- a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpointTest.java +++ b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpointTest.java @@ -8,8 +8,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 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.service.DashboardMetricsService; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -66,4 +71,126 @@ void getOpeningsSubmissionTrends_orgUnitFilter_shouldSucceed() throws Exception .andExpect(status().isNoContent()) .andReturn(); } + + @Test + @DisplayName("Free growing milestones test with no filters should succeed") + void getFreeGrowingMilestonesData_noFilters_shouldSucceed() throws Exception { + DashboardFiltesDto filtersDto = new DashboardFiltesDto(null, null, null, null, null); + + FreeGrowingMilestonesDto milestonesDto = + new FreeGrowingMilestonesDto(0, "0 - 5 months", 25, new BigDecimal("100")); + when(dashboardMetricsService.getFreeGrowingMilestoneChartData(filtersDto)) + .thenReturn(List.of(milestonesDto)); + + mockMvc + .perform( + get("/api/dashboard-metrics/free-growing-milestones") + .with(csrf().asHeader()) + .header("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$[0].index").value("0")) + .andExpect(jsonPath("$[0].label").value("0 - 5 months")) + .andExpect(jsonPath("$[0].amount").value("25")) + .andExpect(jsonPath("$[0].percentage").value(new BigDecimal("100"))) + .andReturn(); + } + + @Test + @DisplayName("Free growing milestones test with client number filter should succeed") + void getFreeGrowingMilestonesData_clientNumberFilter_shouldSucceed() throws Exception { + List dtoList = new ArrayList<>(); + dtoList.add(new FreeGrowingMilestonesDto(0, "0 - 5 months", 25, new BigDecimal("25"))); + dtoList.add(new FreeGrowingMilestonesDto(1, "6 - 11 months", 25, new BigDecimal("25"))); + dtoList.add(new FreeGrowingMilestonesDto(2, "12 - 17 months", 25, new BigDecimal("25"))); + dtoList.add(new FreeGrowingMilestonesDto(3, "18 months", 25, new BigDecimal("25"))); + + DashboardFiltesDto filtersDto = new DashboardFiltesDto(null, null, null, null, "00012797"); + + when(dashboardMetricsService.getFreeGrowingMilestoneChartData(filtersDto)).thenReturn(dtoList); + + mockMvc + .perform( + get("/api/dashboard-metrics/free-growing-milestones?clientNumber=00012797") + .with(csrf().asHeader()) + .header("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$[0].index").value("0")) + .andExpect(jsonPath("$[0].label").value("0 - 5 months")) + .andExpect(jsonPath("$[0].amount").value("25")) + .andExpect(jsonPath("$[0].percentage").value(new BigDecimal("25"))) + .andExpect(jsonPath("$[1].index").value("1")) + .andExpect(jsonPath("$[1].label").value("6 - 11 months")) + .andExpect(jsonPath("$[1].amount").value("25")) + .andExpect(jsonPath("$[1].percentage").value(new BigDecimal("25"))) + .andExpect(jsonPath("$[2].index").value("2")) + .andExpect(jsonPath("$[2].label").value("12 - 17 months")) + .andExpect(jsonPath("$[2].amount").value("25")) + .andExpect(jsonPath("$[2].percentage").value(new BigDecimal("25"))) + .andExpect(jsonPath("$[3].index").value("3")) + .andExpect(jsonPath("$[3].label").value("18 months")) + .andExpect(jsonPath("$[3].amount").value("25")) + .andExpect(jsonPath("$[3].percentage").value(new BigDecimal("25"))) + .andReturn(); + } + + @Test + @DisplayName("Free growing milestones test with no content should succeed") + void getFreeGrowingMilestonesData_noData_shouldSucceed() throws Exception { + DashboardFiltesDto filtersDto = new DashboardFiltesDto(null, null, null, null, "00012579"); + + when(dashboardMetricsService.getFreeGrowingMilestoneChartData(filtersDto)) + .thenReturn(List.of()); + + mockMvc + .perform( + get("/api/dashboard-metrics/free-growing-milestones") + .with(csrf().asHeader()) + .header("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()) + .andReturn(); + } + + @Test + @DisplayName("User recent actions requests test happy path should succeed") + void getUserRecentOpeningsActions_happyPath_shouldSucceed() throws Exception { + MyRecentActionsRequestsDto actionDto = + new MyRecentActionsRequestsDto( + "Created", 48L, "PEN", "Pending", "2 minutes ago", LocalDateTime.now()); + when(dashboardMetricsService.getUserRecentOpeningsActions()).thenReturn(List.of(actionDto)); + + mockMvc + .perform( + get("/api/dashboard-metrics/my-recent-actions/requests") + .with(csrf().asHeader()) + .header("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$[0].activityType").value("Created")) + .andExpect(jsonPath("$[0].openingId").value("48")) + .andExpect(jsonPath("$[0].statusCode").value("PEN")) + .andExpect(jsonPath("$[0].statusDescription").value("Pending")) + .andExpect(jsonPath("$[0].lastUpdatedLabel").value("2 minutes ago")) + .andReturn(); + } + + @Test + @DisplayName("User recent actions requests test no data should succeed") + void getUserRecentOpeningsActions_noData_shouldSucceed() throws Exception { + when(dashboardMetricsService.getUserRecentOpeningsActions()).thenReturn(List.of()); + + mockMvc + .perform( + get("/api/dashboard-metrics/my-recent-actions/requests") + .with(csrf().asHeader()) + .header("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()) + .andReturn(); + } } diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpointTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpointTest.java index 4c917e66..e5d450ce 100644 --- a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpointTest.java +++ b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpointTest.java @@ -1,5 +1,81 @@ package ca.bc.gov.restapi.results.postgres.endpoint; +import static org.mockito.Mockito.when; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import ca.bc.gov.restapi.results.postgres.dto.MyRecentActionsRequestsDto; +import ca.bc.gov.restapi.results.postgres.service.UserOpeningService; +import java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest(UserOpeningEndpoint.class) +@WithMockUser class UserOpeningEndpointTest { - + + @Autowired private MockMvc mockMvc; + + @MockBean private UserOpeningService userOpeningService; + + @Test + @DisplayName("Get user tracked openings happy path should succeed") + void getUserTrackedOpenings_happyPath_shoudSucceed() throws Exception { + MyRecentActionsRequestsDto action = + new MyRecentActionsRequestsDto( + "Update", 123456L, "APP", "Approved", "2 minutes ago", LocalDateTime.now()); + when(userOpeningService.getUserTrackedOpenings()).thenReturn(List.of(action)); + + mockMvc + .perform( + get("/api/user-openings/dashboard-track-openings") + .with(csrf().asHeader()) + .header("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$[0].activityType").value("Update")) + .andExpect(jsonPath("$[0].openingId").value("123456")) + .andExpect(jsonPath("$[0].statusCode").value("APP")) + .andExpect(jsonPath("$[0].statusDescription").value("Approved")) + .andExpect(jsonPath("$[0].lastUpdatedLabel").value("2 minutes ago")) + .andReturn(); + } + + @Test + @DisplayName("Get user tracked openings no data should succeed") + void getUserTrackedOpenings_noData_shoudSucceed() throws Exception { + when(userOpeningService.getUserTrackedOpenings()).thenReturn(List.of()); + + mockMvc + .perform( + get("/api/user-openings/dashboard-track-openings") + .with(csrf().asHeader()) + .header("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()) + .andReturn(); + } + + void saveUserOpening_happyPath_shoudSucceed() throws Exception { + // + } + + void deleteUserOpening_happyPath_shoudSucceed() throws Exception { + // + } + + void deleteUserOpening_notFound_shoudFail() throws Exception { + // + } } From 8a7a4a5958b3b2b38a2391d5c68531151d28affb Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Wed, 10 Apr 2024 14:50:14 -0300 Subject: [PATCH 17/19] test: add dashboard metrics service unit tests jira issue silva-358 --- .../service/DashboardMetricsService.java | 20 +- .../service/DashboardMetricsServiceTest.java | 200 +++++++++++++++++- 2 files changed, 210 insertions(+), 10 deletions(-) diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsService.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsService.java index f5edd019..f71a7e75 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsService.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsService.java @@ -184,16 +184,20 @@ public List getFreeGrowingMilestoneChartData( String label = labelsMap.get(index); int value = groupList.size(); log.info("{} openings of {} for label '{}'", value, totalRecordsFiltered, label); - BigDecimal percentage = - new BigDecimal(String.valueOf(value)) - .divide( - new BigDecimal(String.valueOf(totalRecordsFiltered)), 10, RoundingMode.HALF_UP) - .setScale(2, RoundingMode.HALF_UP) - .multiply(hundred); + + BigDecimal percentage = BigDecimal.ZERO; + if (totalRecordsFiltered > 0) { + percentage = + new BigDecimal(String.valueOf(value)) + .divide( + new BigDecimal(String.valueOf(totalRecordsFiltered)), 10, RoundingMode.HALF_UP) + .setScale(2, RoundingMode.HALF_UP) + .multiply(hundred); + } if (index < 3) { hundredSum = hundredSum.subtract(percentage); - } else { + } else if (index == 3 && totalRecordsFiltered > 0) { percentage = hundredSum; } @@ -210,7 +214,7 @@ public List getFreeGrowingMilestoneChartData( * @return A list of {@link MyRecentActionsRequestsDto} with 5 rows. */ public List getUserRecentOpeningsActions() { - log.info("Getting the last 5 openings activities for the requests tab"); + log.info("Getting up to the last 5 openings activities for the requests tab"); String userId = loggedUserService.getLoggedUserId(); diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java index db701865..ef622aac 100644 --- a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java +++ b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java @@ -4,10 +4,14 @@ import ca.bc.gov.restapi.results.common.security.LoggedUserService; 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.entity.OpeningsActivityEntity; import ca.bc.gov.restapi.results.postgres.entity.OpeningsLastYearEntity; import ca.bc.gov.restapi.results.postgres.repository.OpeningsActivityRepository; import ca.bc.gov.restapi.results.postgres.repository.OpeningsLastYearRepository; +import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List; import org.junit.jupiter.api.Assertions; @@ -32,6 +36,8 @@ class DashboardMetricsServiceTest { private static final Sort SORT = Sort.by("entryTimestamp").ascending(); + private static final Sort SORT_BY_ID = Sort.by("openingId").ascending(); + private List mockOpeningsEntityList() { LocalDateTime entryTimestamp = LocalDateTime.now(); @@ -42,6 +48,7 @@ private List mockOpeningsEntityList() { entity.setUpdateTimestamp(entryTimestamp); entity.setStatus("APP"); entity.setOrgUnitCode("DCR"); + entity.setClientNumber("00012797"); return List.of(entity); } @@ -113,12 +120,12 @@ void getOpeningsSubmissionTrends_statusFilter_shouldSucceed() { } @Test - @DisplayName("Opening submission trends with Status filter should succeed") + @DisplayName("Opening submission trends with Dates filter should succeed") void getOpeningsSubmissionTrends_datesFilter_shouldSucceed() { - LocalDateTime now = LocalDateTime.now(); List entities = mockOpeningsEntityList(); when(openingsLastYearRepository.findAll(SORT)).thenReturn(entities); + LocalDateTime now = LocalDateTime.now(); LocalDateTime oneMonthBefore = now.minusMonths(1L); LocalDateTime oneMonthLater = now.plusMonths(1L); @@ -135,4 +142,193 @@ void getOpeningsSubmissionTrends_datesFilter_shouldSucceed() { Assertions.assertEquals(monthName, list.get(0).monthName()); Assertions.assertEquals(1, list.get(0).amount()); } + + @Test + @DisplayName("Free growing milestones without filters should succeed") + void getFreeGrowingMilestoneChartData_noFilters_shouldSucceed() { + List entities = mockOpeningsEntityList(); + when(openingsLastYearRepository.findAll(SORT_BY_ID)).thenReturn(entities); + + DashboardFiltesDto filtersDto = new DashboardFiltesDto(null, null, null, null, null); + List list = + dashboardMetricsService.getFreeGrowingMilestoneChartData(filtersDto); + + Assertions.assertFalse(list.isEmpty()); + Assertions.assertEquals(4, list.size()); + Assertions.assertEquals(0, list.get(0).index()); + Assertions.assertEquals("0 - 5 months", list.get(0).label()); + Assertions.assertEquals(1, list.get(0).amount()); + Assertions.assertEquals(new BigDecimal("100.00"), list.get(0).percentage()); + + Assertions.assertEquals(1, list.get(1).index()); + Assertions.assertEquals("6 - 11 months", list.get(1).label()); + Assertions.assertEquals(0, list.get(1).amount()); + Assertions.assertEquals(new BigDecimal("0.00"), list.get(1).percentage()); + + Assertions.assertEquals(2, list.get(2).index()); + Assertions.assertEquals("12 - 17 months", list.get(2).label()); + Assertions.assertEquals(0, list.get(2).amount()); + Assertions.assertEquals(new BigDecimal("0.00"), list.get(2).percentage()); + + Assertions.assertEquals(3, list.get(3).index()); + Assertions.assertEquals("18 months", list.get(3).label()); + Assertions.assertEquals(0, list.get(3).amount()); + Assertions.assertEquals(new BigDecimal("0.00"), list.get(3).percentage()); + } + + @Test + @DisplayName("Free growing milestones with org unit filter should succeed") + void getFreeGrowingMilestoneChartData_orgUnitFilter_shouldSucceed() { + List entities = mockOpeningsEntityList(); + when(openingsLastYearRepository.findAll(SORT_BY_ID)).thenReturn(entities); + + DashboardFiltesDto filtersDto = new DashboardFiltesDto("AAA", null, null, null, null); + List list = + dashboardMetricsService.getFreeGrowingMilestoneChartData(filtersDto); + + Assertions.assertFalse(list.isEmpty()); + Assertions.assertEquals(4, list.size()); + Assertions.assertEquals(0, list.get(0).index()); + Assertions.assertEquals("0 - 5 months", list.get(0).label()); + Assertions.assertEquals(0, list.get(0).amount()); + Assertions.assertEquals(BigDecimal.ZERO, list.get(0).percentage()); + + Assertions.assertEquals(1, list.get(1).index()); + Assertions.assertEquals("6 - 11 months", list.get(1).label()); + Assertions.assertEquals(0, list.get(1).amount()); + Assertions.assertEquals(BigDecimal.ZERO, list.get(1).percentage()); + + Assertions.assertEquals(2, list.get(2).index()); + Assertions.assertEquals("12 - 17 months", list.get(2).label()); + Assertions.assertEquals(0, list.get(2).amount()); + Assertions.assertEquals(BigDecimal.ZERO, list.get(2).percentage()); + + Assertions.assertEquals(3, list.get(3).index()); + Assertions.assertEquals("18 months", list.get(3).label()); + Assertions.assertEquals(0, list.get(3).amount()); + Assertions.assertEquals(BigDecimal.ZERO, list.get(3).percentage()); + } + + @Test + @DisplayName("Free growing milestones with client number filter should succeed") + void getFreeGrowingMilestoneChartData_clientNumberFilter_shouldSucceed() { + List entities = mockOpeningsEntityList(); + when(openingsLastYearRepository.findAll(SORT_BY_ID)).thenReturn(entities); + + DashboardFiltesDto filtersDto = new DashboardFiltesDto(null, null, null, null, "00011254"); + List list = + dashboardMetricsService.getFreeGrowingMilestoneChartData(filtersDto); + + Assertions.assertFalse(list.isEmpty()); + Assertions.assertEquals(4, list.size()); + Assertions.assertEquals(0, list.get(0).index()); + Assertions.assertEquals("0 - 5 months", list.get(0).label()); + Assertions.assertEquals(0, list.get(0).amount()); + Assertions.assertEquals(BigDecimal.ZERO, list.get(0).percentage()); + + Assertions.assertEquals(1, list.get(1).index()); + Assertions.assertEquals("6 - 11 months", list.get(1).label()); + Assertions.assertEquals(0, list.get(1).amount()); + Assertions.assertEquals(BigDecimal.ZERO, list.get(1).percentage()); + + Assertions.assertEquals(2, list.get(2).index()); + Assertions.assertEquals("12 - 17 months", list.get(2).label()); + Assertions.assertEquals(0, list.get(2).amount()); + Assertions.assertEquals(BigDecimal.ZERO, list.get(2).percentage()); + + Assertions.assertEquals(3, list.get(3).index()); + Assertions.assertEquals("18 months", list.get(3).label()); + Assertions.assertEquals(0, list.get(3).amount()); + Assertions.assertEquals(BigDecimal.ZERO, list.get(3).percentage()); + } + + @Test + @DisplayName("Free growing milestones with dates filter should succeed") + void getFreeGrowingMilestoneChartData_datesFilter_shouldSucceed() { + List entities = mockOpeningsEntityList(); + when(openingsLastYearRepository.findAll(SORT_BY_ID)).thenReturn(entities); + + LocalDateTime now = LocalDateTime.now(); + LocalDateTime oneMonthBefore = now.minusMonths(1L); + LocalDateTime oneMonthLater = now.plusMonths(1L); + + DashboardFiltesDto filtersDto = + new DashboardFiltesDto(null, null, oneMonthBefore, oneMonthLater, null); + List list = + dashboardMetricsService.getFreeGrowingMilestoneChartData(filtersDto); + + Assertions.assertFalse(list.isEmpty()); + Assertions.assertEquals(4, list.size()); + Assertions.assertEquals(0, list.get(0).index()); + Assertions.assertEquals("0 - 5 months", list.get(0).label()); + Assertions.assertEquals(1, list.get(0).amount()); + Assertions.assertEquals(new BigDecimal("100.00"), list.get(0).percentage()); + + Assertions.assertEquals(1, list.get(1).index()); + Assertions.assertEquals("6 - 11 months", list.get(1).label()); + Assertions.assertEquals(0, list.get(1).amount()); + Assertions.assertEquals(new BigDecimal("0.00"), list.get(1).percentage()); + + Assertions.assertEquals(2, list.get(2).index()); + Assertions.assertEquals("12 - 17 months", list.get(2).label()); + Assertions.assertEquals(0, list.get(2).amount()); + Assertions.assertEquals(new BigDecimal("0.00"), list.get(2).percentage()); + + Assertions.assertEquals(3, list.get(3).index()); + Assertions.assertEquals("18 months", list.get(3).label()); + Assertions.assertEquals(0, list.get(3).amount()); + Assertions.assertEquals(new BigDecimal("0.00"), list.get(3).percentage()); + } + + @Test + @DisplayName("My recent activities request table data test happy path should succeed") + void getUserRecentOpeningsActions_happyPath_shouldSucceed() { + String userId = "TEST"; + + when(loggedUserService.getLoggedUserId()).thenReturn(userId); + + Sort sort = Sort.by("lastUpdated").descending(); + OpeningsActivityEntity activity = new OpeningsActivityEntity(); + activity.setOpeningId(112233L); + // If you're here looking for more codes and descriptions, you may want to take a look on + // the jira issue: https://apps.nrs.gov.bc.ca/int/jira/browse/SILVA-362 look for the comment + // with the 'fire' emoji, made on 20/Mar/24 3:03 PM + activity.setActivityTypeCode("UPD"); + activity.setActivityTypeDesc("Update"); + activity.setStatusCode("APP"); + activity.setStatusDesc("Approved"); + activity.setLastUpdated(LocalDateTime.now().minusHours(2)); + activity.setEntryUserid(userId); + + when(openingsActivityRepository.findAllByEntryUserid(userId, sort)) + .thenReturn(List.of(activity)); + + List dtoList = + dashboardMetricsService.getUserRecentOpeningsActions(); + + Assertions.assertFalse(dtoList.isEmpty()); + Assertions.assertEquals(1, dtoList.size()); + Assertions.assertEquals("Update", dtoList.get(0).activityType()); + Assertions.assertEquals(112233L, dtoList.get(0).openingId()); + Assertions.assertEquals("APP", dtoList.get(0).statusCode()); + Assertions.assertEquals("Approved", dtoList.get(0).statusDescription()); + Assertions.assertEquals("2 hours ago", dtoList.get(0).lastUpdatedLabel()); + } + + @Test + @DisplayName("My recent activities request table data test no data should succeed") + void getUserRecentOpeningsActions_noData_shouldSucceed() { + String userId = "TEST"; + + when(loggedUserService.getLoggedUserId()).thenReturn(userId); + + Sort sort = Sort.by("lastUpdated").descending(); + when(openingsActivityRepository.findAllByEntryUserid(userId, sort)) + .thenReturn(List.of()); + + List dtoList = + dashboardMetricsService.getUserRecentOpeningsActions(); + + Assertions.assertTrue(dtoList.isEmpty()); + } } From b1fc13fd209992d92ca7cb7feb4ea952dc66e8f4 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Wed, 10 Apr 2024 19:32:10 -0300 Subject: [PATCH 18/19] test: add user opening service unit tests jira issue silva-358 --- .../service/DashboardMetricsServiceTest.java | 2 +- .../service/UserOpeningServiceTest.java | 122 +++++++++++++++++- 2 files changed, 122 insertions(+), 2 deletions(-) diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java index ef622aac..839f6fdc 100644 --- a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java +++ b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/DashboardMetricsServiceTest.java @@ -287,7 +287,6 @@ void getUserRecentOpeningsActions_happyPath_shouldSucceed() { when(loggedUserService.getLoggedUserId()).thenReturn(userId); - Sort sort = Sort.by("lastUpdated").descending(); OpeningsActivityEntity activity = new OpeningsActivityEntity(); activity.setOpeningId(112233L); // If you're here looking for more codes and descriptions, you may want to take a look on @@ -300,6 +299,7 @@ void getUserRecentOpeningsActions_happyPath_shouldSucceed() { activity.setLastUpdated(LocalDateTime.now().minusHours(2)); activity.setEntryUserid(userId); + Sort sort = Sort.by("lastUpdated").descending(); when(openingsActivityRepository.findAllByEntryUserid(userId, sort)) .thenReturn(List.of(activity)); diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningServiceTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningServiceTest.java index 13562413..7e60b157 100644 --- a/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningServiceTest.java +++ b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/service/UserOpeningServiceTest.java @@ -1,5 +1,125 @@ package ca.bc.gov.restapi.results.postgres.service; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; + +import ca.bc.gov.restapi.results.common.exception.UserOpeningNotFoundException; +import ca.bc.gov.restapi.results.common.security.LoggedUserService; +import ca.bc.gov.restapi.results.postgres.dto.MyRecentActionsRequestsDto; +import ca.bc.gov.restapi.results.postgres.entity.OpeningsActivityEntity; +import ca.bc.gov.restapi.results.postgres.entity.UserOpeningEntity; +import ca.bc.gov.restapi.results.postgres.repository.OpeningsActivityRepository; +import ca.bc.gov.restapi.results.postgres.repository.UserOpeningRepository; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) class UserOpeningServiceTest { - + + @Mock LoggedUserService loggedUserService; + + @Mock UserOpeningRepository userOpeningRepository; + + @Mock OpeningsActivityRepository openingsActivityRepository; + + private UserOpeningService userOpeningService; + + private static final String USER_ID = "TEST"; + + @BeforeEach + void setup() { + this.userOpeningService = + new UserOpeningService( + loggedUserService, userOpeningRepository, openingsActivityRepository); + } + + @Test + @DisplayName("Get user tracked openings happy path should succeed") + void getUserTrackedOpenings_happyPath_shouldSucceed() { + when(loggedUserService.getLoggedUserId()).thenReturn(USER_ID); + + UserOpeningEntity entity = new UserOpeningEntity(); + entity.setUserId(USER_ID); + entity.setOpeningId(223344L); + + when(userOpeningRepository.findAllByUserId(USER_ID)).thenReturn(List.of(entity)); + + LocalDateTime now = LocalDateTime.now().minusMinutes(2); + OpeningsActivityEntity openingEntity = new OpeningsActivityEntity(); + openingEntity.setOpeningId(entity.getOpeningId()); + openingEntity.setActivityTypeCode("UPD"); + openingEntity.setActivityTypeDesc("Update"); + openingEntity.setStatusCode("APP"); + openingEntity.setStatusDesc("Approved"); + openingEntity.setLastUpdated(now); + openingEntity.setEntryUserid(USER_ID); + + when(openingsActivityRepository.findAllById(List.of(223344L))) + .thenReturn(List.of(openingEntity)); + + List openings = userOpeningService.getUserTrackedOpenings(); + + Assertions.assertFalse(openings.isEmpty()); + Assertions.assertEquals("Update", openings.get(0).activityType()); + Assertions.assertEquals(223344L, openings.get(0).openingId()); + Assertions.assertEquals("APP", openings.get(0).statusCode()); + Assertions.assertEquals("Approved", openings.get(0).statusDescription()); + Assertions.assertEquals("2 minutes ago", openings.get(0).lastUpdatedLabel()); + } + + @Test + @DisplayName("Get user tracked openings no data should succeed") + void getUserTrackedOpenings_noData_shouldSucceed() { + when(loggedUserService.getLoggedUserId()).thenReturn(USER_ID); + + when(userOpeningRepository.findAllByUserId(USER_ID)).thenReturn(List.of()); + + List openings = userOpeningService.getUserTrackedOpenings(); + + Assertions.assertTrue(openings.isEmpty()); + } + + @Test + @DisplayName("Save opening to user happy path shoudl succeed") + void saveOpeningToUser_happyPath_shouldSucceed() { + when(loggedUserService.getLoggedUserId()).thenReturn(USER_ID); + when(userOpeningRepository.saveAndFlush(any())).thenReturn(new UserOpeningEntity()); + userOpeningService.saveOpeningToUser(112233L); + } + + @Test + @DisplayName("Delete opening from user's favourite happy path should succeed") + void deleteOpeningFromUserFavourite_happyPath_shouldSucceed() { + when(loggedUserService.getLoggedUserId()).thenReturn(USER_ID); + + UserOpeningEntity userEntity = new UserOpeningEntity(); + when(userOpeningRepository.findById(any())).thenReturn(Optional.of(userEntity)); + + doNothing().when(userOpeningRepository).delete(any()); + doNothing().when(userOpeningRepository).flush(); + + userOpeningService.deleteOpeningFromUserFavourite(112233L); + } + + @Test + @DisplayName("Delete opening from user's favourite not found should fail") + void deleteOpeningFromUserFavourite_notFound_shouldFail() { + when(loggedUserService.getLoggedUserId()).thenReturn(USER_ID); + when(userOpeningRepository.findById(any())).thenReturn(Optional.empty()); + + Assertions.assertThrows( + UserOpeningNotFoundException.class, + () -> { + userOpeningService.deleteOpeningFromUserFavourite(112233L); + }); + } } From 4c2410aecbd9be0f24e0489bb2b791edad5dff36 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Thu, 11 Apr 2024 08:21:37 -0300 Subject: [PATCH 19/19] docs: improve wordy for swagger open api jira issue silva-358 --- .../postgres/dto/FreeGrowingMilestonesDto.java | 14 +++++++++++++- .../endpoint/DashboardMetricsEndpoint.java | 4 +++- .../postgres/endpoint/UserOpeningEndpoint.java | 4 ++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/FreeGrowingMilestonesDto.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/FreeGrowingMilestonesDto.java index d41ea9bf..bb823946 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/FreeGrowingMilestonesDto.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/dto/FreeGrowingMilestonesDto.java @@ -1,7 +1,19 @@ package ca.bc.gov.restapi.results.postgres.dto; +import io.swagger.v3.oas.annotations.media.Schema; 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) {} + @Schema(description = "Number representing the index, between 0 and 3.", example = "1") + Integer index, + @Schema( + description = "Label of the current slice. E.g.: 0 - 5 month, 6 - 11 months", + example = "12 - 17 months") + String label, + @Schema(description = "Amount of openings in the slice", example = "33") Integer amount, + @Schema( + description = + "Percentage of this slice considering the total of openings on the period.", + example = "28") + BigDecimal percentage) {} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java index 2b886761..d33e5895 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/DashboardMetricsEndpoint.java @@ -122,7 +122,9 @@ public ResponseEntity> getOpeningsSubmissionTrends( @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.", + description = + "Fetches data from the last 24 months and group them into periods for the Free growing" + + " chart.", responses = { @ApiResponse( responseCode = "200", diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpoint.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpoint.java index fc0523e9..3f783bab 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpoint.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/endpoint/UserOpeningEndpoint.java @@ -104,8 +104,8 @@ public ResponseEntity saveUserOpening( description = "Allow users to remove saved Opening IDs from their favourite list.", responses = { @ApiResponse( - responseCode = "200", - description = "Opening successfully removed for the user."), + responseCode = "204", + description = "Opening successfully removed. No content body."), @ApiResponse( responseCode = "401", description = "Access token is missing or invalid",