From 59a2ec8dbc01ef4e0b1c847a889e727fb9f72068 Mon Sep 17 00:00:00 2001 From: Paulo Gomes da Cruz Junior Date: Fri, 8 Nov 2024 16:48:17 -0800 Subject: [PATCH] fix(SILVA-516): fixing my recent actions (#454) --- .../configuration/SilvaConfiguration.java | 11 +- .../oracle/endpoint/UserActionsEndpoint.java | 35 +++++ .../entity/ResultsAuditEventProjection.java | 14 ++ .../oracle/enums/AuditActionCodeEnum.java | 41 +++++ .../ResultsAuditActivityRepository.java | 33 ++++ .../oracle/service/UserActionsService.java | 45 ++++++ .../endpoint/DashboardMetricsEndpoint.java | 17 -- .../OpeningsActivityRepository.java | 6 +- .../service/DashboardMetricsService.java | 145 ++++++++---------- backend/src/main/resources/application.yml | 2 + .../oracle/enums/AuditActionCodeEnumTest.java | 50 ++++++ .../DashboardMetricsEndpointTest.java | 41 ----- .../UserActionsEndpointIntegrationTest.java | 63 ++++++++ .../service/DashboardMetricsServiceTest.java | 6 +- .../test/resources/application-default.yml | 2 + .../oracle/V003__oracle_test_data.sql | 18 +++ .../components/MyRecentActions.test.tsx | 44 ++++-- .../components/OpeningMetricsTab.test.tsx | 14 +- .../src/__test__/screens/Opening.test.tsx | 6 +- .../__test__/services/OpeningService.test.ts | 31 +++- .../src/components/ActionsTable/index.tsx | 31 ++-- .../src/components/ActivityTag/definitions.ts | 19 ++- frontend/src/components/ActivityTag/index.tsx | 17 +- frontend/src/components/DynamicIcon/index.tsx | 24 +++ .../components/MyRecentActions/filesData.ts | 48 +----- .../src/components/MyRecentActions/index.tsx | 95 +++++++----- .../OpeningScreenDataTable/index.tsx | 1 + frontend/src/components/OpeningsTab/index.tsx | 3 +- frontend/src/services/OpeningService.ts | 21 ++- frontend/src/types/ActivityTagType.ts | 22 +++ 30 files changed, 613 insertions(+), 292 deletions(-) create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/oracle/endpoint/UserActionsEndpoint.java create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/oracle/entity/ResultsAuditEventProjection.java create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/oracle/enums/AuditActionCodeEnum.java create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/oracle/repository/ResultsAuditActivityRepository.java create mode 100644 backend/src/main/java/ca/bc/gov/restapi/results/oracle/service/UserActionsService.java create mode 100644 backend/src/test/java/ca/bc/gov/restapi/results/oracle/enums/AuditActionCodeEnumTest.java create mode 100644 backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/UserActionsEndpointIntegrationTest.java create mode 100644 frontend/src/components/DynamicIcon/index.tsx diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/common/configuration/SilvaConfiguration.java b/backend/src/main/java/ca/bc/gov/restapi/results/common/configuration/SilvaConfiguration.java index 8af1dc84..5c1c09df 100644 --- a/backend/src/main/java/ca/bc/gov/restapi/results/common/configuration/SilvaConfiguration.java +++ b/backend/src/main/java/ca/bc/gov/restapi/results/common/configuration/SilvaConfiguration.java @@ -32,15 +32,24 @@ public class SilvaConfiguration { private ExternalApiAddress forestClientApi; @NestedConfigurationProperty private ExternalApiAddress openMaps; + @NestedConfigurationProperty + private SilvaDataLimits limits; @Data @Builder @NoArgsConstructor @AllArgsConstructor public static class ExternalApiAddress { - private String address; private String key; } + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class SilvaDataLimits { + private Integer maxActionsResults; + } + } diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/oracle/endpoint/UserActionsEndpoint.java b/backend/src/main/java/ca/bc/gov/restapi/results/oracle/endpoint/UserActionsEndpoint.java new file mode 100644 index 00000000..59f7e96e --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/oracle/endpoint/UserActionsEndpoint.java @@ -0,0 +1,35 @@ +package ca.bc.gov.restapi.results.oracle.endpoint; + +import ca.bc.gov.restapi.results.oracle.service.UserActionsService; +import ca.bc.gov.restapi.results.postgres.dto.MyRecentActionsRequestsDto; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +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.RestController; + +@RestController +@RequestMapping("/api/users") +@RequiredArgsConstructor +@Slf4j +public class UserActionsEndpoint { + + private final UserActionsService userActionsService; + + @GetMapping("/recent-actions") + public ResponseEntity> getUserRecentOpeningsActions() { + List actionsDto = + userActionsService.getResultsAuditActivity(); + + log.info("Returning {} recent actions", actionsDto.size()); + + if (actionsDto.isEmpty()) { + return ResponseEntity.noContent().build(); + } + + return ResponseEntity.ok(actionsDto); + } + +} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/oracle/entity/ResultsAuditEventProjection.java b/backend/src/main/java/ca/bc/gov/restapi/results/oracle/entity/ResultsAuditEventProjection.java new file mode 100644 index 00000000..d1902b44 --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/oracle/entity/ResultsAuditEventProjection.java @@ -0,0 +1,14 @@ +package ca.bc.gov.restapi.results.oracle.entity; + +import java.time.LocalDateTime; + +public interface ResultsAuditEventProjection { + Long getResultsAuditEventId(); + Long getOpeningId(); + String getActionCode(); + String getActionCodeDescription(); + String getCategoryCode(); + String getCategoryCodeDescription(); + LocalDateTime getEntryTimestamp(); + String getEntryUserid(); +} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/oracle/enums/AuditActionCodeEnum.java b/backend/src/main/java/ca/bc/gov/restapi/results/oracle/enums/AuditActionCodeEnum.java new file mode 100644 index 00000000..81c009ab --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/oracle/enums/AuditActionCodeEnum.java @@ -0,0 +1,41 @@ +package ca.bc.gov.restapi.results.oracle.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public enum AuditActionCodeEnum { + UPD("UPD", "Update"), + COR("COR", "Correction"), + O("O", "Original"), + SEC197("197", "Section 197"), + AMG("AMG", "Amalgamate"), + ES("ES", "E-submission"), + MIL("MIL", "Milestone"), + MIN("MIN", "Amended (Minor)"), + SPA("SPA", "Site Plan Amendment"), + VAR("VAR", "Variation"), + AMD("AMD", "Amended"), + APP("APP", "Approved"), + DEL("DEL", "Deleted"), + REJ("REJ", "Rejected"), + RET("RET", "Retired"), + RMD("RMD", "Removed"), + SUB("SUB", "Submitted"); + + private final String code; + private final String description; + + public static AuditActionCodeEnum of(String code) { + for (AuditActionCodeEnum value : AuditActionCodeEnum.values()) { + if (value.getCode().equals(code)) { + return value; + } + } + return null; + } +} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/oracle/repository/ResultsAuditActivityRepository.java b/backend/src/main/java/ca/bc/gov/restapi/results/oracle/repository/ResultsAuditActivityRepository.java new file mode 100644 index 00000000..00c320b8 --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/oracle/repository/ResultsAuditActivityRepository.java @@ -0,0 +1,33 @@ +package ca.bc.gov.restapi.results.oracle.repository; + +import ca.bc.gov.restapi.results.oracle.entity.OpeningEntity; +import ca.bc.gov.restapi.results.oracle.entity.ResultsAuditEventProjection; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface ResultsAuditActivityRepository extends JpaRepository { + + @Query( + value = """ + SELECT + rae.results_audit_event_id, + rae.opening_id, + rae.results_audit_action_code as action_code, + raac.description as action_code_description, + o.open_category_code as category_code, + occ.description as category_code_description, + rae.entry_timestamp, + rae.entry_userid + FROM THE.RESULTS_AUDIT_EVENT rae + LEFT JOIN THE.RESULTS_AUDIT_ACTION_CODE raac ON raac.RESULTS_AUDIT_ACTION_CODE = rae.RESULTS_AUDIT_ACTION_CODE + LEFT JOIN THE.OPENING o ON o.OPENING_ID = rae.OPENING_ID + LEFT JOIN THE.OPEN_CATEGORY_CODE occ ON occ.OPEN_CATEGORY_CODE = o.OPEN_CATEGORY_CODE + WHERE rae.ENTRY_USERID = ?1 + ORDER BY rae.ENTRY_TIMESTAMP DESC + FETCH FIRST ?2 ROWS ONLY""", + nativeQuery = true) + List findUserRecentAuditEvents(String userId, Integer limit); + + +} diff --git a/backend/src/main/java/ca/bc/gov/restapi/results/oracle/service/UserActionsService.java b/backend/src/main/java/ca/bc/gov/restapi/results/oracle/service/UserActionsService.java new file mode 100644 index 00000000..6f72d32c --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/restapi/results/oracle/service/UserActionsService.java @@ -0,0 +1,45 @@ +package ca.bc.gov.restapi.results.oracle.service; + +import ca.bc.gov.restapi.results.common.configuration.SilvaConfiguration; +import ca.bc.gov.restapi.results.common.security.LoggedUserService; +import ca.bc.gov.restapi.results.oracle.repository.ResultsAuditActivityRepository; +import ca.bc.gov.restapi.results.postgres.dto.MyRecentActionsRequestsDto; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ocpsoft.prettytime.PrettyTime; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class UserActionsService { + + private final ResultsAuditActivityRepository auditActivityRepository; + private final LoggedUserService loggedUserService; + private final SilvaConfiguration silvaConfiguration; + private final PrettyTime prettyTime = new PrettyTime(); + + public List getResultsAuditActivity() { + String userId = loggedUserService.getLoggedUserId(); + + log.info("Getting recent audit events for user {}", userId); + + return + auditActivityRepository + .findUserRecentAuditEvents(userId, silvaConfiguration.getLimits().getMaxActionsResults()) + .stream() + .map(entity -> + new MyRecentActionsRequestsDto( + entity.getActionCodeDescription(), + entity.getOpeningId(), + entity.getCategoryCode(), + entity.getCategoryCodeDescription(), + prettyTime.format(entity.getEntryTimestamp()), + entity.getEntryTimestamp() + ) + ) + .toList(); + } + +} 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 9fb42c5b..1542d5f7 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,7 +3,6 @@ import ca.bc.gov.restapi.results.common.util.TimestampUtil; import ca.bc.gov.restapi.results.postgres.dto.DashboardFiltersDto; 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.util.List; @@ -98,20 +97,4 @@ 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") - 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/repository/OpeningsActivityRepository.java b/backend/src/main/java/ca/bc/gov/restapi/results/postgres/repository/OpeningsActivityRepository.java index a1c956ec..e7ec3384 100644 --- 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 @@ -3,7 +3,7 @@ import ca.bc.gov.restapi.results.postgres.entity.OpeningsActivityEntity; import ca.bc.gov.restapi.results.postgres.entity.OpeningsActivityEntityId; import java.util.List; -import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; /** @@ -12,7 +12,5 @@ public interface OpeningsActivityRepository extends JpaRepository { - List findAllByOpeningIdIn(List openingIds); - - List findAllByEntryUserid(String userId, Sort sort); + List findAllByEntryUserid(String userId, Pageable page); } 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 2e87214e..9e4c62fb 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 @@ -24,6 +24,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ocpsoft.prettytime.PrettyTime; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; @@ -39,6 +40,8 @@ public class DashboardMetricsService { private final LoggedUserService loggedUserService; + private final PrettyTime prettyTime = new PrettyTime(); + /** * Get openings submission trends data for the opening per year chart. * @@ -75,61 +78,6 @@ public List getOpeningsSubmissionTrends(DashboardFiltersDto return chartData; } - private void filterOpeningSubmissions( - Map> resultMap, - List entities, - DashboardFiltersDto filters) { - // 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()) - && !filters.orgUnit().equals(entity.getOrgUnitCode())) { - continue; - } - - // Status filter - if (!Objects.isNull(filters.status()) && !filters.status().equals(entity.getStatus())) { - 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; - } - - resultMap.get(entity.getEntryTimestamp().getMonthValue()).add(entity); - } - } - - private Map> createBaseMonthsMap( - Map monthNamesMap, Integer firstMonth) { - Map> resultMap = new LinkedHashMap<>(); - - // Fill with 12 months - log.info("First month: {}", firstMonth); - while (resultMap.size() < 12) { - resultMap.put(firstMonth, new ArrayList<>()); - - String monthName = Month.of(firstMonth).name().toLowerCase(); - monthName = monthName.substring(0, 1).toUpperCase() + monthName.substring(1, 3); - monthNamesMap.put(firstMonth, monthName); - - firstMonth += 1; - if (firstMonth == 13) { - firstMonth = 1; - } - } - - return resultMap; - } - /** * Get free growing milestone declarations data for the chart. * @@ -235,42 +183,85 @@ public List getFreeGrowingMilestoneChartData( public List getUserRecentOpeningsActions() { log.info("Getting up to 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); + openingsActivityRepository + .findAllByEntryUserid( + loggedUserService.getLoggedUserId(), + PageRequest.of(0, 5, Sort.by("lastUpdated").descending()) + ); if (openingList.isEmpty()) { log.info("No recent activities data found for the user!"); return List.of(); } + return + openingList + .stream() + .map(entity -> + new MyRecentActionsRequestsDto( + Objects.toString(entity.getActivityTypeDesc(), "Created"), + entity.getOpeningId(), + entity.getStatusCode(), + entity.getStatusDesc(), + prettyTime.format(entity.getLastUpdated()), + entity.getLastUpdated() + ) + ) + .toList(); + } + + private void filterOpeningSubmissions( + Map> resultMap, + List entities, + DashboardFiltersDto filters) { + // 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()) + && !filters.orgUnit().equals(entity.getOrgUnitCode())) { + continue; + } - PrettyTime prettyTime = new PrettyTime(); + // Status filter + if (!Objects.isNull(filters.status()) && !filters.status().equals(entity.getStatus())) { + continue; + } + + // Entry start date filter + if (!Objects.isNull(filters.entryDateStart()) + && entity.getEntryTimestamp().isBefore(filters.entryDateStart())) { + continue; + } - List chartData = new ArrayList<>(); - for (OpeningsActivityEntity entity : openingList) { - String statusDesc = entity.getActivityTypeDesc(); - if (Objects.isNull(statusDesc)) { - statusDesc = "Created"; + // Entry end date filter + if (!Objects.isNull(filters.entryDateEnd()) + && entity.getEntryTimestamp().isAfter(filters.entryDateEnd())) { + continue; } - MyRecentActionsRequestsDto dto = - new MyRecentActionsRequestsDto( - statusDesc, - entity.getOpeningId(), - entity.getStatusCode(), - entity.getStatusDesc(), - prettyTime.format(entity.getLastUpdated()), - entity.getLastUpdated()); + resultMap.get(entity.getEntryTimestamp().getMonthValue()).add(entity); + } + } + + private Map> createBaseMonthsMap( + Map monthNamesMap, Integer firstMonth) { + Map> resultMap = new LinkedHashMap<>(); + + // Fill with 12 months + log.info("First month: {}", firstMonth); + while (resultMap.size() < 12) { + resultMap.put(firstMonth, new ArrayList<>()); - chartData.add(dto); + String monthName = Month.of(firstMonth).name().toLowerCase(); + monthName = monthName.substring(0, 1).toUpperCase() + monthName.substring(1, 3); + monthNamesMap.put(firstMonth, monthName); - if (chartData.size() == 5) { - break; + firstMonth += 1; + if (firstMonth == 13) { + firstMonth = 1; } } - return chartData; + return resultMap; } } diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 10478eea..c135222a 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -101,6 +101,8 @@ ca: keystore: ${ORACLEDB_KEYSTORE:jssecacerts-path} secret: ${ORACLEDB_SECRET:changeit} host: ${DATABASE_HOST:nrcdb03.bcgov} + limits: + max-actions-results: ${MAX_ACTIONS_RESULTS:5} # Logging logging: diff --git a/backend/src/test/java/ca/bc/gov/restapi/results/oracle/enums/AuditActionCodeEnumTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/oracle/enums/AuditActionCodeEnumTest.java new file mode 100644 index 00000000..011758bd --- /dev/null +++ b/backend/src/test/java/ca/bc/gov/restapi/results/oracle/enums/AuditActionCodeEnumTest.java @@ -0,0 +1,50 @@ +package ca.bc.gov.restapi.results.oracle.enums; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +@DisplayName("Unit Test | AuditActionCodeEnum") +class AuditActionCodeEnumTest { + + @DisplayName("Test enum conversion") + @ParameterizedTest(name = "Test enum conversion for code {0} and description {1}") + @MethodSource("enumConversion") + void testEnumConversion(String code, String description, AuditActionCodeEnum expected) { + AuditActionCodeEnum actual = AuditActionCodeEnum.of(code); + assertEquals(expected, actual); + if (expected != null) { + assertEquals(expected.getCode(), actual.getCode()); + assertEquals(description, actual.getDescription()); + } + } + + private static Stream enumConversion() { + return Stream.of( + Arguments.of("UPD", "Update", AuditActionCodeEnum.UPD), + Arguments.of("COR", "Correction", AuditActionCodeEnum.COR), + Arguments.of("O", "Original", AuditActionCodeEnum.O), + Arguments.of("197", "Section 197", AuditActionCodeEnum.SEC197), + Arguments.of("AMG", "Amalgamate", AuditActionCodeEnum.AMG), + Arguments.of("ES", "E-submission", AuditActionCodeEnum.ES), + Arguments.of("MIL", "Milestone", AuditActionCodeEnum.MIL), + Arguments.of("MIN", "Amended (Minor)", AuditActionCodeEnum.MIN), + Arguments.of("SPA", "Site Plan Amendment", AuditActionCodeEnum.SPA), + Arguments.of("VAR", "Variation", AuditActionCodeEnum.VAR), + Arguments.of("AMD", "Amended", AuditActionCodeEnum.AMD), + Arguments.of("APP", "Approved", AuditActionCodeEnum.APP), + Arguments.of("DEL", "Deleted", AuditActionCodeEnum.DEL), + Arguments.of("REJ", "Rejected", AuditActionCodeEnum.REJ), + Arguments.of("RET", "Retired", AuditActionCodeEnum.RET), + Arguments.of("RMD", "Removed", AuditActionCodeEnum.RMD), + Arguments.of("SUB", "Submitted", AuditActionCodeEnum.SUB), + Arguments.of(null, null, null), + Arguments.of("", null, null) + ); + } + +} \ No newline at end of file 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 733c517a..c09eefcf 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 @@ -9,11 +9,9 @@ import ca.bc.gov.restapi.results.postgres.dto.DashboardFiltersDto; 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; @@ -154,43 +152,4 @@ void getFreeGrowingMilestonesData_noData_shouldSucceed() throws Exception { .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/UserActionsEndpointIntegrationTest.java b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/UserActionsEndpointIntegrationTest.java new file mode 100644 index 00000000..2ad33680 --- /dev/null +++ b/backend/src/test/java/ca/bc/gov/restapi/results/postgres/endpoint/UserActionsEndpointIntegrationTest.java @@ -0,0 +1,63 @@ +package ca.bc.gov.restapi.results.postgres.endpoint; + +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.extensions.AbstractTestContainerIntegrationTest; +import ca.bc.gov.restapi.results.extensions.WithMockJwt; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +@DisplayName("Integration Test | User Actions Endpoint") +@TestMethodOrder(OrderAnnotation.class) +@AutoConfigureMockMvc +@WithMockJwt +class UserActionsEndpointIntegrationTest extends AbstractTestContainerIntegrationTest { + + @Autowired + private MockMvc mockMvc; + + @Test + @DisplayName("User recent actions requests test happy path should succeed") + void getUserRecentOpeningsActions_happyPath_shouldSucceed() throws Exception { + + mockMvc + .perform( + get("/api/users/recent-actions") + .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("Submitted")) + .andExpect(jsonPath("$[0].openingId").value("102")) + .andExpect(jsonPath("$[0].statusCode").value("FTML")) + .andExpect(jsonPath("$[0].statusDescription").value("Forest Tenure - Major Licensee")) + .andReturn(); + } + + @Test + @DisplayName("User recent actions requests test no data should succeed") + @WithMockJwt(value = "no-data") + void getUserRecentOpeningsActions_noData_shouldSucceed() throws Exception { + + mockMvc + .perform( + get("/api/users/recent-actions") + .with(csrf().asHeader()) + .header("Content-Type", MediaType.APPLICATION_JSON_VALUE) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()) + .andReturn(); + } + +} \ No newline at end of file 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 70165580..bd2b4459 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 @@ -22,6 +22,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; @ExtendWith(MockitoExtension.class) @@ -368,7 +369,8 @@ void getUserRecentOpeningsActions_happyPath_shouldSucceed() { activity.setEntryUserid(userId); Sort sort = Sort.by("lastUpdated").descending(); - when(openingsActivityRepository.findAllByEntryUserid(userId, sort)) + when(openingsActivityRepository.findAllByEntryUserid(userId, + PageRequest.of(0,5,sort))) .thenReturn(List.of(activity)); List dtoList = @@ -391,7 +393,7 @@ void getUserRecentOpeningsActions_noData_shouldSucceed() { when(loggedUserService.getLoggedUserId()).thenReturn(userId); Sort sort = Sort.by("lastUpdated").descending(); - when(openingsActivityRepository.findAllByEntryUserid(userId, sort)).thenReturn(List.of()); + when(openingsActivityRepository.findAllByEntryUserid(userId, PageRequest.of(0,5,sort))).thenReturn(List.of()); List dtoList = dashboardMetricsService.getUserRecentOpeningsActions(); diff --git a/backend/src/test/resources/application-default.yml b/backend/src/test/resources/application-default.yml index 400798cf..f49ddb23 100644 --- a/backend/src/test/resources/application-default.yml +++ b/backend/src/test/resources/application-default.yml @@ -8,6 +8,8 @@ spring: jwt: issuer-uri: https://aws-cognito-issuer-uri.aws.com jwk-set-uri: https://aws-cognito-issuer-uri.aws.com/.well-known/jwks.json + jpa: + show-sql: true # Users allowed to see and download WMS layers information ca: diff --git a/backend/src/test/resources/migration/oracle/V003__oracle_test_data.sql b/backend/src/test/resources/migration/oracle/V003__oracle_test_data.sql index f9f50115..06427a81 100644 --- a/backend/src/test/resources/migration/oracle/V003__oracle_test_data.sql +++ b/backend/src/test/resources/migration/oracle/V003__oracle_test_data.sql @@ -152,6 +152,23 @@ INSERT INTO THE.RESULTS_AUDIT_EVENT ENTRY_TIMESTAMP ) VALUES(1, 101, NULL, NULL, 'SUB', TO_DATE('2024-01-20 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 'Forest Cover 0000000', NULL, 'Y', 101, 101, 'TEST\OTTOMATED', TO_DATE('2024-01-20 00:00:00', 'YYYY-MM-DD HH24:MI:SS')); +INSERT INTO THE.RESULTS_AUDIT_EVENT +( + RESULTS_AUDIT_EVENT_ID, + OPENING_ID, + STANDARDS_REGIME_ID, + SILVICULTURE_PROJECT_ID, + RESULTS_AUDIT_ACTION_CODE, + ACTION_DATE, + DESCRIPTION, + USER_ID, + EMAIL_SENT_IND, + XML_SUBMISSION_ID, + OPENING_AMENDMENT_NUMBER, + ENTRY_USERID, + ENTRY_TIMESTAMP +) +VALUES(2, 102, NULL, NULL, 'SUB', TO_DATE('2024-01-20 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 'Forest Cover 0000000', NULL, 'Y', 102, 102, 'IDIR@TEST', TO_DATE('2024-01-20 00:00:00', 'YYYY-MM-DD HH24:MI:SS')); INSERT INTO THE.ACTIVITY_TREATMENT_UNIT ( @@ -284,3 +301,4 @@ INSERT INTO THE.STOCKING_MILESTONE EXTENT_FEASIBLE_DECLARED_IND ) VALUES(101, 'FG', NULL, NULL, NULL, 'N', NULL, 12, 15, NULL, NULL, 'RESULTS_CONV', TO_DATE('2024-01-20 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 'RESULTS_CONV', TO_DATE('2024-01-20 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), 1, 'N'); + diff --git a/frontend/src/__test__/components/MyRecentActions.test.tsx b/frontend/src/__test__/components/MyRecentActions.test.tsx index cce74f3a..8f7aba12 100644 --- a/frontend/src/__test__/components/MyRecentActions.test.tsx +++ b/frontend/src/__test__/components/MyRecentActions.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { render, act, waitFor } from '@testing-library/react'; import { describe, expect, it, vi } from 'vitest'; import MyRecentActions from '../../components/MyRecentActions'; +import { fetchRecentActions } from '../../services/OpeningService'; const data = { "activityType": "Update", @@ -13,36 +14,49 @@ const data = { }; vi.mock('../../services/OpeningService', () => ({ - fetchRecentActions: vi.fn(() => [ - { - activityType: data.activityType, - openingId: data.openingId.toString(), - statusCode: data.statusCode, - statusDescription: data.statusDescription, - lastUpdated: data.lastUpdated, - lastUpdatedLabel: data.lastUpdatedLabel - } - ]), + fetchRecentActions: vi.fn(), })); describe('My Recent Actions component tests', () => { it('should render the recent action component', async () => { - const { getByTestId, getByText } = render( - - ); + (fetchRecentActions as vi.Mock).mockResolvedValue([ + { + activityType: data.activityType, + openingId: data.openingId.toString(), + statusCode: data.statusCode, + statusDescription: data.statusDescription, + lastUpdated: data.lastUpdated, + lastUpdatedLabel: data.lastUpdatedLabel + } + ]); + + let getByTestId; + let getByText; + await act(async () =>{ ({ getByTestId, getByText } = render());}); + const element: HTMLElement | null = await waitFor(() => getByTestId('my-recent-actions__recent-tab-header')); expect(element).toBeDefined(); expect(element).not.toBeNull(); expect(element?.classList.contains('tab-header-recent')).toBe(true); - const text = 'Recent'; + const text = 'Update'; act(() => { const elementText: HTMLElement = getByText(text); - expect(elementText.textContent).toEqual(text); + expect(elementText.textContent).toContain(text); }); }); it('should render the files and tabs action component', async () => { + (fetchRecentActions as vi.Mock).mockResolvedValue([ + { + activityType: data.activityType, + openingId: data.openingId.toString(), + statusCode: data.statusCode, + statusDescription: data.statusDescription, + lastUpdated: data.lastUpdated, + lastUpdatedLabel: data.lastUpdatedLabel + } + ]); const { getByTestId, getByText } = render( ); diff --git a/frontend/src/__test__/components/OpeningMetricsTab.test.tsx b/frontend/src/__test__/components/OpeningMetricsTab.test.tsx index 3f9678b0..b646da02 100644 --- a/frontend/src/__test__/components/OpeningMetricsTab.test.tsx +++ b/frontend/src/__test__/components/OpeningMetricsTab.test.tsx @@ -4,7 +4,7 @@ import { render, act, waitFor, screen } from '@testing-library/react'; import OpeningMetricsTab from '../../components/OpeningMetricsTab'; import { NotificationProvider } from '../../contexts/NotificationProvider'; import { fetchOpeningFavourites } from '../../services/OpeningFavouriteService'; -import { fetchFreeGrowingMilestones, fetchOpeningsPerYear, fetchRecentOpenings } from '../../services/OpeningService'; +import { fetchFreeGrowingMilestones, fetchOpeningsPerYear, fetchRecentOpenings, fetchRecentActions } from '../../services/OpeningService'; vi.mock('../../services/OpeningFavouriteService', () => ({ fetchOpeningFavourites: vi.fn(), @@ -15,7 +15,8 @@ vi.mock('../../services/OpeningService', async () => { ...actual, fetchRecentOpenings: vi.fn(), fetchOpeningsPerYear: vi.fn(), - fetchFreeGrowingMilestones: vi.fn(), + fetchFreeGrowingMilestones: vi.fn(), + fetchRecentActions: vi.fn(), }; }); @@ -26,6 +27,15 @@ describe('OpeningMetricsTab', () => { (fetchOpeningsPerYear as vi.Mock).mockResolvedValue([]); (fetchFreeGrowingMilestones as vi.Mock).mockResolvedValue([{ group: '1-5', value: 11 }]); (fetchOpeningFavourites as vi.Mock).mockResolvedValue([1, 2, 3]); + (fetchRecentActions as vi.Mock).mockResolvedValue([ + { + activityType: "Update", + openingId: "1541297", + statusCode: "APP", + lastUpdated: "2024-05-16T19:59:21.635Z", + lastUpdatedLabel: "1 minute ago" + } + ]); }); diff --git a/frontend/src/__test__/screens/Opening.test.tsx b/frontend/src/__test__/screens/Opening.test.tsx index 04b6c8f4..c5c623e2 100644 --- a/frontend/src/__test__/screens/Opening.test.tsx +++ b/frontend/src/__test__/screens/Opening.test.tsx @@ -7,7 +7,7 @@ import { NotificationProvider } from '../../contexts/NotificationProvider'; import { BrowserRouter } from 'react-router-dom'; import { RecentOpening } from '../../types/RecentOpening'; import { getWmsLayersWhitelistUsers } from '../../services/SecretsService'; -import { fetchFreeGrowingMilestones, fetchOpeningsPerYear, fetchRecentOpenings } from '../../services/OpeningService'; +import { fetchFreeGrowingMilestones, fetchOpeningsPerYear, fetchRecentOpenings, fetchRecentActions } from '../../services/OpeningService'; import { fetchOpeningFavourites } from '../../services/OpeningFavouriteService'; import { AuthProvider } from '../../contexts/AuthProvider'; @@ -34,7 +34,8 @@ vi.mock('../../services/OpeningService', async () => { ...actual, fetchRecentOpenings: vi.fn(), fetchOpeningsPerYear: vi.fn(), - fetchFreeGrowingMilestones: vi.fn(), + fetchFreeGrowingMilestones: vi.fn(), + fetchRecentActions: vi.fn(), }; }); @@ -85,6 +86,7 @@ describe('Opening screen test cases', () => { ]); (fetchFreeGrowingMilestones as vi.Mock).mockResolvedValue([{ group: '1-5', value: 11 }]); (fetchOpeningFavourites as vi.Mock).mockResolvedValue([1,2,3]); + (fetchRecentActions as vi.Mock).mockResolvedValue([data]); diff --git a/frontend/src/__test__/services/OpeningService.test.ts b/frontend/src/__test__/services/OpeningService.test.ts index b3427fc3..665fc031 100644 --- a/frontend/src/__test__/services/OpeningService.test.ts +++ b/frontend/src/__test__/services/OpeningService.test.ts @@ -127,10 +127,35 @@ describe('OpeningService', () => { }); describe('fetchRecentActions', () => { - it('should fetch recent actions successfully', () => { - const result = fetchRecentActions(); + it('should fetch recent actions successfully', async () => { + const mockData = { + "activityType": "Update", + "openingId": "1541297", + "statusCode": "APP", + "statusDescription": "Approved", + "lastUpdatedLabel": "1 minute ago", + "lastUpdated": "2024-05-16T19:59:21.635Z" + }; + (axios.get as vi.Mock).mockResolvedValue({ data: [mockData] }); + + const result = await fetchRecentActions(); + + expect(result).toEqual([ + { + activityType: mockData.activityType, + openingId: mockData.openingId.toString(), + statusCode: mockData.statusCode, + statusDescription: mockData.statusDescription, + lastUpdated: mockData.lastUpdated, + lastUpdatedLabel: mockData.lastUpdatedLabel + } + ]); + }); + + it('should handle error while fetching recent actions', async () => { + (axios.get as vi.Mock).mockRejectedValue(new Error('Network Error')); - expect(result).toEqual([]); + await expect(fetchRecentActions()).rejects.toThrow('Network Error'); }); }); diff --git a/frontend/src/components/ActionsTable/index.tsx b/frontend/src/components/ActionsTable/index.tsx index 0dc84b85..7769f59a 100644 --- a/frontend/src/components/ActionsTable/index.tsx +++ b/frontend/src/components/ActionsTable/index.tsx @@ -1,3 +1,4 @@ +import { ReactNode } from 'react'; import { Table, TableHead, @@ -15,6 +16,7 @@ import { ITableHeader } from '../../types/TableHeader'; interface IActionsTable { readonly rows: RecentAction[]; readonly headers: ITableHeader[]; + readonly emptySection?: ReactNode; } /** @@ -25,19 +27,10 @@ interface IActionsTable { * @returns {JSX.Element} The rendered ActionsTable component. */ function ActionsTable(props: IActionsTable): JSX.Element { - const getTypeEnum = (typeStr: string): ActivityTagTypeEnum => { - switch (typeStr) { - case ActivityTagTypeEnum.OPENING_DETAILS: - return ActivityTagTypeEnum.OPENING_DETAILS; - case ActivityTagTypeEnum.OPENING_REPORT: - return ActivityTagTypeEnum.OPENING_REPORT; - case ActivityTagTypeEnum.OPENING_SPATIAL: - return ActivityTagTypeEnum.OPENING_SPATIAL; - case ActivityTagTypeEnum.UPDATE: - return ActivityTagTypeEnum.UPDATE; - default: - return ActivityTagTypeEnum.UNKNOWN; - } + const getTypeEnum = (value: string): ActivityTagTypeEnum => { + // Find the enum entry by value, or fall back to UNKNOWN if not found + const match = (Object.values(ActivityTagTypeEnum) as string[]).find(enumValue => enumValue === value); + return (match as ActivityTagTypeEnum) || ActivityTagTypeEnum.UNKNOWN; }; const getFileFormatEnum = (formatStr: string): ActivityTagFileFormatEnum => { @@ -56,6 +49,7 @@ function ActionsTable(props: IActionsTable): JSX.Element { const headerKeys = props.headers.map(header => header.key); return ( + <> @@ -87,8 +81,17 @@ function ActionsTable(props: IActionsTable): JSX.Element { ))} ))} - + {props.rows.length === 0 && ( + + + {props.emptySection} + + + )} +
+ + ); }; diff --git a/frontend/src/components/ActivityTag/definitions.ts b/frontend/src/components/ActivityTag/definitions.ts index f3a82aed..4fe1a9b7 100644 --- a/frontend/src/components/ActivityTag/definitions.ts +++ b/frontend/src/components/ActivityTag/definitions.ts @@ -1,10 +1,27 @@ -export const ActivityIconMap = { +export const ActivityIconMap: Record = { 'Opening details': 'MapBoundary', 'Opening report': 'Document', 'Opening spatial': 'Map', 'Update': 'Map', + 'Correction': 'SpellCheck', + 'Original': 'Document', + 'Section 197': 'Help', + 'Amalgamate': 'GroupAccess', + 'E-submission': 'IbmCloudant', + 'Milestone': 'Milestone', + 'Amended (Minor)': 'SpellCheck', + 'Site Plan Amendment': 'SpellCheck', + 'Variation': 'Help', + 'Amended': 'Stamp', + 'Approved': 'CheckmarkOutline', + 'Deleted': 'TrashCan', + 'Rejected': 'ErrorFilled', + 'Retired': 'Hourglass', + 'Removed': 'TrashCan', + 'Submitted': 'MailAll', 'Unknown': 'Help' }; + export const FileIconMap = { 'PDF file': 'DocumentPdf', 'CSV file': 'Document', diff --git a/frontend/src/components/ActivityTag/index.tsx b/frontend/src/components/ActivityTag/index.tsx index 64a99ec0..d4be5e2f 100644 --- a/frontend/src/components/ActivityTag/index.tsx +++ b/frontend/src/components/ActivityTag/index.tsx @@ -1,5 +1,4 @@ -import React from 'react'; -import * as Carbon from '@carbon/icons-react'; +import DynamicIcon from '../DynamicIcon'; import { ActivityIconMap, FileIconMap } from './definitions'; import './styles.scss'; import { ActivityTagFileFormatEnum, ActivityTagTypeEnum } from '../../types/ActivityTagType'; @@ -10,19 +9,11 @@ type ActivityTagProps = { }; const ActivityTag = ({ type, fileFormat }: ActivityTagProps) => { - const tagType: keyof typeof ActivityIconMap = Object.keys(ActivityIconMap).includes(type) ? type : 'Unknown'; - let iconName; - if (fileFormat && FileIconMap[fileFormat]) { - iconName = FileIconMap[fileFormat]; - } - else{ - iconName = ActivityIconMap[tagType]; // get the icon name by the type name - } - const Icon = Carbon[iconName]; // get the icon component by the icon name - + const iconName = (fileFormat && FileIconMap[fileFormat]) ? FileIconMap[fileFormat] : ActivityIconMap[type]; + return ( <> - {type} +  {type} ); }; diff --git a/frontend/src/components/DynamicIcon/index.tsx b/frontend/src/components/DynamicIcon/index.tsx new file mode 100644 index 00000000..9cf5c4d2 --- /dev/null +++ b/frontend/src/components/DynamicIcon/index.tsx @@ -0,0 +1,24 @@ +import * as CarbonIcons from '@carbon/icons-react'; +import { FunctionComponent } from 'react'; + +// Define a type for the props your icon component will accept +interface IconProps { + iconName: string; + size?: number; +} + +const DynamicIcon: FunctionComponent = ({ iconName, size = 24 }) => { + // Dynamically access the icon using bracket notation + const IconComponent = (CarbonIcons as Record>)[iconName]; + + // Check if the icon exists, if not render a fallback (optional) + if (!IconComponent) { + console.warn(`Icon "${iconName}" not found in Carbon icons`); + return Icon Not Found; + } + + // Render the icon component + return ; +}; + +export default DynamicIcon; \ No newline at end of file diff --git a/frontend/src/components/MyRecentActions/filesData.ts b/frontend/src/components/MyRecentActions/filesData.ts index 3df019a5..3a6c4912 100644 --- a/frontend/src/components/MyRecentActions/filesData.ts +++ b/frontend/src/components/MyRecentActions/filesData.ts @@ -1,53 +1,7 @@ import { RecentAction } from "../../types/RecentAction"; import { ITableHeader } from "../../types/TableHeader"; -export const rows: RecentAction[] = [ - { - activityType: 'Opening details', - openingId: '11', - statusCode: 'PND', - statusDescription: 'Pending', - fileFormat: 'PDF file', - lastUpdated: '-', - lastUpdatedLabel: 'Now' - }, - { - activityType: 'Opening report', - openingId: '12', - statusCode: 'SUB', - statusDescription: 'Submitted', - fileFormat: 'Excel file', - lastUpdated: '-', - lastUpdatedLabel: '1 minute ago' - }, - { - activityType: 'Opening spatial', - openingId: '13', - statusCode: 'APP', - statusDescription: 'Approved', - fileFormat: 'CSV file', - lastUpdated: '-', - lastUpdatedLabel: 'Aug 20, 2023' - }, - { - activityType:'Opening spatial', - openingId: '14', - statusCode: 'APP', - statusDescription: 'Approved', - fileFormat: 'PDF file', - lastUpdated: '-', - lastUpdatedLabel:'Nov 20, 2023' - }, - { - activityType:'Opening details', - openingId: '15', - statusCode: 'PRG', - statusDescription: 'In progress', - fileFormat: 'Excel file', - lastUpdated: '-', - lastUpdatedLabel:'Now' - } -]; +export const rows: RecentAction[] = []; export const headers: ITableHeader[] = [ { diff --git a/frontend/src/components/MyRecentActions/index.tsx b/frontend/src/components/MyRecentActions/index.tsx index 83e19468..20ed2413 100644 --- a/frontend/src/components/MyRecentActions/index.tsx +++ b/frontend/src/components/MyRecentActions/index.tsx @@ -6,44 +6,40 @@ import { rows as fileRows, headers as fileHeaders } from "./filesData"; import { RecentAction } from '../../types/RecentAction'; import { ITableHeader } from '../../types/TableHeader'; import './styles.scss' +import EmptySection from '../EmptySection'; + +const headers: ITableHeader[] = [ + { + key: 'activityType', + header: 'Activity Type', + selected: true + }, + { + key: 'openingId', + header: 'Opening ID', + selected: true + }, + { + key: 'statusCode', + header: 'Status', + selected: true + }, + { + key: 'lastUpdatedLabel', + header: 'Last Updated', + selected: true + } +]; const MyRecentActions: React.FC = () => { const [recentActions, setRecentActions] = useState([]); - const headers: ITableHeader[] = [ - { - key: 'activityType', - header: 'Activity Type', - selected: true - }, - { - key: 'openingId', - header: 'Opening ID', - selected: true - }, - { - key: 'statusCode', - header: 'Status', - selected: true - }, - { - key: 'lastUpdatedLabel', - header: 'Last Updated', - selected: true - } - ]; + const fetchData = async () => { + const actions: RecentAction[] = await fetchRecentActions(); + setRecentActions(actions); + }; - useEffect(() => { - function fetchData() { - try { - const actions: RecentAction[] = fetchRecentActions(); - setRecentActions(actions); - } catch (error) { - console.error('Error fetching recent actions:', error); - } - } - fetchData(); - }, []); + useEffect(() => { fetchData(); }, []); return (
@@ -54,10 +50,10 @@ const MyRecentActions: React.FC = () => { className="tab-header-recent" data-testid={"my-recent-actions__recent-tab-header"} > - Recent + Actions
- +
{ - - + + } + /> - - {/* fileRows and fileHeaders are still static */} - {/* Empty rows for the "Files and Docs" tab */} + + } + /> diff --git a/frontend/src/components/OpeningScreenDataTable/index.tsx b/frontend/src/components/OpeningScreenDataTable/index.tsx index 5c4be756..a2ac1101 100644 --- a/frontend/src/components/OpeningScreenDataTable/index.tsx +++ b/frontend/src/components/OpeningScreenDataTable/index.tsx @@ -131,6 +131,7 @@ const OpeningScreenDataTable: React.FC = ({ onKeyDown={(e: any) => handleSearchChange(e)} placeholder="Filter by opening ID, File ID, timber mark, cut block, status..." persistent + disabled={rows.length === 0} /> console.log('Download Click')} disabled={selectedRows.length === 0}> diff --git a/frontend/src/components/OpeningsTab/index.tsx b/frontend/src/components/OpeningsTab/index.tsx index 1f86b85c..d1f9642f 100644 --- a/frontend/src/components/OpeningsTab/index.tsx +++ b/frontend/src/components/OpeningsTab/index.tsx @@ -73,8 +73,9 @@ const OpeningsTab: React.FC = ({ showSpatial, setShowSpatial }) => { renderIcon={Location} type="button" onClick={() => toggleSpatial()} + disabled > - {showSpatial ? 'Hide Spatial' : 'Show Spatial'} + {showSpatial ? 'Hide map' : 'Show map'}
{showSpatial ? ( diff --git a/frontend/src/services/OpeningService.ts b/frontend/src/services/OpeningService.ts index 7ec438f7..e6265936 100644 --- a/frontend/src/services/OpeningService.ts +++ b/frontend/src/services/OpeningService.ts @@ -152,19 +152,16 @@ export async function fetchFreeGrowingMilestones(props: IFreeGrowingProps): Prom * * @returns {RecentAction[]} Array with recent action objects. */ -export function fetchRecentActions(): RecentAction[] { - // const authToken = getAuthIdToken(); +export async function fetchRecentActions(): Promise { + const authToken = getAuthIdToken(); try { - // Comment out the actual API call for now - // const response = await axios.get(backendUrl.concat("/api/dashboard-metrics/my-recent-actions/requests")); - // headers: { - // Authorization: `Bearer ${authToken}` - // } - // }); - - // Temporarily use the sample data for testing - // const { data } = response; - const data: RecentAction[] = []; + const response = await axios.get(backendUrl.concat("/api/users/recent-actions"),{ + headers: { + Authorization: `Bearer ${authToken}` + } + }); + + const { data } = response; if (Array.isArray(data)) { // Transforming response data into a format consumable by the component diff --git a/frontend/src/types/ActivityTagType.ts b/frontend/src/types/ActivityTagType.ts index d3a0ff90..c6ed395d 100644 --- a/frontend/src/types/ActivityTagType.ts +++ b/frontend/src/types/ActivityTagType.ts @@ -3,12 +3,34 @@ export enum ActivityTagTypeEnum { OPENING_REPORT = 'Opening report', OPENING_SPATIAL = 'Opening spatial', UPDATE = 'Update', + CORRECTION = 'Correction', + ORIGINAL = 'Original', + SECTION_197 = 'Section 197', + AMALGAMATE = 'Amalgamate', + E_SUBMISSION = 'E-submission', + MILESTONE = 'Milestone', + AMENDED_MINOR = 'Amended (Minor)', + SITE_PLAN_AMENDMENT = 'Site Plan Amendment', + VARIATION = 'Variation', + AMENDED = 'Amended', + APPROVED = 'Approved', + DELETED = 'Deleted', + REJECTED = 'Rejected', + RETIRED = 'Retired', + REMOVED = 'Removed', + SUBMITTED = 'Submitted', UNKNOWN = 'Unknown' } +export type ActivityTagType = keyof typeof ActivityTagTypeEnum; + export enum ActivityTagFileFormatEnum { PDF_FILE = 'PDF file', CSV_FILE = 'CSV file', EXCEL_FILE = 'Excel file', UNKNOWN = 'Unknown' } + + + +