From 238830e99b28ef0a0b06889cd8038734ea677217 Mon Sep 17 00:00:00 2001 From: Nikos Kyriakoulis Date: Mon, 5 May 2025 12:16:36 +0300 Subject: [PATCH 1/8] Created metrics endpoints for TMF ServiceOrder related information --- .../metrics/api/ServiceOrderMetricsApi.java | 72 +++++ .../api/ServiceOrderMetricsApiController.java | 140 ++++++++++ .../ServiceOrderMetricsRepoService.java | 74 +++++ .../so641/repo/ServiceOrderRepository.java | 24 ++ .../ServiceOrderMetricsApiControllerTest.java | 254 ++++++++++++++++++ 5 files changed, 564 insertions(+) create mode 100644 src/main/java/org/etsi/osl/tmf/metrics/api/ServiceOrderMetricsApi.java create mode 100644 src/main/java/org/etsi/osl/tmf/metrics/api/ServiceOrderMetricsApiController.java create mode 100644 src/main/java/org/etsi/osl/tmf/metrics/reposervices/ServiceOrderMetricsRepoService.java create mode 100644 src/test/java/org/etsi/osl/services/api/metrics/ServiceOrderMetricsApiControllerTest.java diff --git a/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceOrderMetricsApi.java b/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceOrderMetricsApi.java new file mode 100644 index 00000000..ff31e118 --- /dev/null +++ b/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceOrderMetricsApi.java @@ -0,0 +1,72 @@ +package org.etsi.osl.tmf.metrics.api; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.etsi.osl.tmf.common.model.service.ServiceStateType; +import org.etsi.osl.tmf.so641.model.ServiceOrderStateType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import java.time.OffsetDateTime; +import java.util.Map; + +@Tag(name = "ServiceOrderMetricsApi") +public interface ServiceOrderMetricsApi { + + Logger log = LoggerFactory.getLogger(ServiceOrderMetricsApi.class); + + @Operation(summary = "Get total number of service orders", operationId = "getTotalServiceOrders") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "400", description = "Bad Request"), + @ApiResponse(responseCode = "500", description = "Internal Server Error") + }) + @RequestMapping(value = "/tmf-api/metrics/totalServiceOrders", method = RequestMethod.GET, produces = "application/json;charset=utf-8") + ResponseEntity> getTotalServiceOrders( + @Valid @RequestParam(value = "state", required = false) ServiceOrderStateType state + ); + + + @Operation(summary = "Get total number of active service orders", operationId = "getTotalActiveServiceOrders") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "400", description = "Bad Request"), + @ApiResponse(responseCode = "500", description = "Internal Server Error") + }) + @RequestMapping(value = "/tmf-api/metrics/activeServiceOrders", method = RequestMethod.GET, produces = "application/json;charset=utf-8") + ResponseEntity> getTotalActiveServiceOrders(); + + + @Operation(summary = "Get service orders grouped by day", operationId = "getServiceOrdersGroupedByDay") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "400", description = "Bad Request"), + @ApiResponse(responseCode = "500", description = "Internal Server Error") + }) + @RequestMapping(value = "/tmf-api/metrics/serviceOrdersGroupByDay", method = RequestMethod.GET, produces = "application/json;charset=utf-8") + ResponseEntity> getServiceOrdersGroupedByDay( + @Valid @RequestParam(value = "starttime", required = true) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime starttime, + @Valid @RequestParam(value = "endtime", required = true) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime endtime + ); + + + @Operation(summary = "Get service orders grouped by state", operationId = "getServiceOrdersGroupedByState") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "400", description = "Bad Request"), + @ApiResponse(responseCode = "500", description = "Internal Server Error") + }) + @RequestMapping(value = "/tmf-api/metrics/serviceOrdersGroupByState", method = RequestMethod.GET, produces = "application/json;charset=utf-8") + ResponseEntity> getServiceOrdersGroupedByState( + @Valid @RequestParam(value = "starttime", required = true) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime starttime, + @Valid @RequestParam(value = "endtime", required = true) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime endtime + ); +} diff --git a/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceOrderMetricsApiController.java b/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceOrderMetricsApiController.java new file mode 100644 index 00000000..e337e28f --- /dev/null +++ b/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceOrderMetricsApiController.java @@ -0,0 +1,140 @@ +package org.etsi.osl.tmf.metrics.api; + +import org.etsi.osl.tmf.metrics.reposervices.ServiceOrderMetricsRepoService; +import org.etsi.osl.tmf.so641.model.ServiceOrderStateType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; + +import java.time.OffsetDateTime; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + + +@Controller +public class ServiceOrderMetricsApiController implements ServiceOrderMetricsApi{ + + private static final Logger log = LoggerFactory.getLogger(ServiceOrderMetricsApiController.class); + private final ServiceOrderMetricsRepoService serviceOrderMetricsRepoService; + + @Autowired + public ServiceOrderMetricsApiController(ServiceOrderMetricsRepoService serviceOrderMetricsRepoService) { + this.serviceOrderMetricsRepoService = serviceOrderMetricsRepoService; + } + + @Override + public ResponseEntity> getTotalServiceOrders(ServiceOrderStateType state) { + try { + int totalServiceOrders = serviceOrderMetricsRepoService.countTotalServiceOrders(state); + Map response = new HashMap<>(); + response.put("totalServiceOrders", totalServiceOrders); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + log.error("Couldn't retrieve total service orders. ", e); + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @Override + public ResponseEntity> getTotalActiveServiceOrders() { + try { + int totalActiveServiceOrders = serviceOrderMetricsRepoService.countTotalActiveServiceOrders(); + Map response = new HashMap<>(); + response.put("activeServiceOrders", totalActiveServiceOrders); + return new ResponseEntity<>(response, HttpStatus.OK); + } catch (Exception e) { + log.error("Couldn't retrieve total active service orders. ", e); + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @Override + public ResponseEntity> getServiceOrdersGroupedByDay(OffsetDateTime starttime, OffsetDateTime endtime) { + try { + Map orderDatesGroupedByDate = serviceOrderMetricsRepoService.getServiceOrdersGroupedByDay(starttime, endtime); + + // Fill missing days with count 0 + Map fullDayMap = new LinkedHashMap<>(); + OffsetDateTime cursor = starttime.truncatedTo(ChronoUnit.DAYS); + OffsetDateTime endDay = endtime.truncatedTo(ChronoUnit.DAYS); + while (!cursor.isAfter(endDay)) { + String key = cursor.toInstant().toString(); + fullDayMap.put(key, orderDatesGroupedByDate.getOrDefault(key, 0L)); + cursor = cursor.plusDays(1); + } + + List> groupByDayList = fullDayMap.entrySet().stream() + .map(entry -> { + Map dayMap = new HashMap<>(); + dayMap.put("key", entry.getKey()); + dayMap.put("count", entry.getValue()); + return dayMap; + }) + .toList(); + + Map aggregations = Map.of("groupByDay", groupByDayList); + Map serviceOrders = Map.of( + "total", fullDayMap.values().stream().mapToLong(Long::longValue).sum(), + "aggregations", aggregations + ); + + Map response = Map.of("serviceOrders", serviceOrders); + + return new ResponseEntity<>(response, HttpStatus.OK); + + } catch (Exception e) { + log.error("Couldn't retrieve services grouped by state. ", e); + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + @Override + public ResponseEntity> getServiceOrdersGroupedByState(OffsetDateTime starttime, OffsetDateTime endtime) { + try { + Map servicesByState = serviceOrderMetricsRepoService.getServiceOrdersGroupedByState(starttime, endtime); + + // Initialize with all possible states and 0. Ensures that all states are represented, even if not present in the data. + Map fullStateMap = new LinkedHashMap<>(); + for (ServiceOrderStateType state : ServiceOrderStateType.values()) { + fullStateMap.put(state.name(), 0); + } + + // Overwrite counts with actual data + servicesByState.forEach((key, value) -> { + fullStateMap.put(key.toUpperCase(), value); + }); + + // Build groupByState list + List> groupByStateList = fullStateMap.entrySet().stream() + .map(entry -> { + Map map = new HashMap<>(); + map.put("key", entry.getKey()); + map.put("count", entry.getValue()); + return map; + }) + .toList(); + + + // Wrap in response structure + Map aggregations = Map.of("groupByState", groupByStateList); + Map services = Map.of( + "total", fullStateMap.values().stream().mapToInt(Integer::intValue).sum(), + "aggregations", aggregations + ); + + Map response = Map.of("serviceOrders", services); + return new ResponseEntity<>(response, HttpStatus.OK); + + } catch (Exception e) { + log.error("Couldn't retrieve services grouped by state. ", e); + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } + +} diff --git a/src/main/java/org/etsi/osl/tmf/metrics/reposervices/ServiceOrderMetricsRepoService.java b/src/main/java/org/etsi/osl/tmf/metrics/reposervices/ServiceOrderMetricsRepoService.java new file mode 100644 index 00000000..508d9a63 --- /dev/null +++ b/src/main/java/org/etsi/osl/tmf/metrics/reposervices/ServiceOrderMetricsRepoService.java @@ -0,0 +1,74 @@ +package org.etsi.osl.tmf.metrics.reposervices; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.etsi.osl.tmf.so641.model.ServiceOrderStateType; +import org.etsi.osl.tmf.so641.repo.ServiceOrderRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.OffsetDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +public class ServiceOrderMetricsRepoService { + + @Autowired + ObjectMapper objectMapper; + + @Autowired + ServiceOrderRepository serviceOrderRepository; + + public int countTotalServiceOrders(ServiceOrderStateType state) { + if (state == null) { + return serviceOrderRepository.countAll(); + } else { + return serviceOrderRepository.countByState(state); + } + } + + public int countTotalActiveServiceOrders() { + OffsetDateTime currentDate = OffsetDateTime.now(); + List activeStates = List.of( + ServiceOrderStateType.INPROGRESS, + ServiceOrderStateType.COMPLETED + ); + + return serviceOrderRepository.countAllActive(currentDate, activeStates); + } + + public Map getServiceOrdersGroupedByDay(OffsetDateTime starttime, OffsetDateTime endtime) { + if (starttime.plusDays(31).isBefore(endtime)) { + starttime = endtime.minusDays(31); + } + + List orderDates = serviceOrderRepository.getOrderDatesBetweenDates(starttime, endtime); + + return orderDates.stream() + .map(dt -> dt.truncatedTo(ChronoUnit.DAYS)) // Remove time portion + .collect(Collectors.groupingBy( + dt -> dt.toInstant().toString(), // Format as ISO string (Z) + Collectors.counting() + )); + + + + } + + public Map getServiceOrdersGroupedByState(OffsetDateTime starttime, OffsetDateTime endtime) { + if (starttime.plusDays(31).isBefore(endtime)) { + starttime = endtime.minusDays(31); + } + + List rawResults = serviceOrderRepository.groupByStateBetweenDates(starttime, endtime); + + return rawResults.stream() + .collect(Collectors.toMap( + row -> row[0].toString(), + row -> ((Number) row[1]).intValue() + )); + } + +} diff --git a/src/main/java/org/etsi/osl/tmf/so641/repo/ServiceOrderRepository.java b/src/main/java/org/etsi/osl/tmf/so641/repo/ServiceOrderRepository.java index c717e063..253e3176 100644 --- a/src/main/java/org/etsi/osl/tmf/so641/repo/ServiceOrderRepository.java +++ b/src/main/java/org/etsi/osl/tmf/so641/repo/ServiceOrderRepository.java @@ -19,6 +19,7 @@ */ package org.etsi.osl.tmf.so641.repo; +import java.time.OffsetDateTime; import java.util.List; import java.util.Optional; import org.etsi.osl.tmf.common.model.UserPartRoleType; @@ -51,4 +52,27 @@ public interface ServiceOrderRepository extends JpaRepository findNotesOfServOrder(String id); + + + // Methods for metrics + + @Query("SELECT COUNT(sor) FROM ServiceOrder sor") + int countAll(); + + int countByState(ServiceOrderStateType state); + + @Query("SELECT COUNT(sor) FROM ServiceOrder sor " + + "WHERE sor.state IN :states " + + "AND sor.requestedStartDate < :currentDate AND sor.requestedCompletionDate > :currentDate") + int countAllActive(OffsetDateTime currentDate, List states); + + @Query("SELECT sor.state, COUNT(sor) FROM ServiceOrder sor " + + "WHERE sor.requestedStartDate >= :starttime AND sor.requestedCompletionDate <= :endtime " + + "GROUP BY sor.state") + List groupByStateBetweenDates(OffsetDateTime starttime, OffsetDateTime endtime); + + @Query("SELECT sor.orderDate FROM ServiceOrder sor " + + "WHERE sor.orderDate >= :starttime AND sor.orderDate <= :endtime") + List getOrderDatesBetweenDates(OffsetDateTime starttime, OffsetDateTime endtime); + } diff --git a/src/test/java/org/etsi/osl/services/api/metrics/ServiceOrderMetricsApiControllerTest.java b/src/test/java/org/etsi/osl/services/api/metrics/ServiceOrderMetricsApiControllerTest.java new file mode 100644 index 00000000..fcd6e5a0 --- /dev/null +++ b/src/test/java/org/etsi/osl/services/api/metrics/ServiceOrderMetricsApiControllerTest.java @@ -0,0 +1,254 @@ +package org.etsi.osl.services.api.metrics; + +import com.jayway.jsonpath.JsonPath; +import org.etsi.osl.tmf.JsonUtils; +import org.etsi.osl.tmf.OpenAPISpringBoot; +import org.etsi.osl.tmf.so641.model.*; +import org.etsi.osl.tmf.so641.reposervices.ServiceOrderRepoService; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.WebApplicationContext; + +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(SpringRunner.class) +@Transactional +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.MOCK, + classes = OpenAPISpringBoot.class +) +//@AutoConfigureTestDatabase //this automatically uses h2 +@AutoConfigureMockMvc +@ActiveProfiles("testing") +//@TestPropertySource( +// locations = "classpath:application-testing.yml") +public class ServiceOrderMetricsApiControllerTest { + + @Autowired + private MockMvc mvc; + + @Autowired + ServiceOrderRepoService serviceOrderRepoService; + + @Autowired + private WebApplicationContext context; + + @Before + public void setup() throws Exception { + mvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(springSecurity()) + .build(); + } + + @WithMockUser(username="osadmin", roles = {"ADMIN","USER"}) + @Test + public void testCountTotalServiceOrders() throws Exception { + createServiceOrder(ServiceOrderStateType.INPROGRESS); + + String response = mvc.perform(MockMvcRequestBuilders.get("/tmf-api/metrics/totalServiceOrders" ) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk() ) + .andReturn().getResponse().getContentAsString(); + + int totalServiceOrders = JsonPath.read(response, "$.totalServiceOrders"); + + + assertThat(totalServiceOrders).isEqualTo(serviceOrderRepoService.findAll().size()); + } + + @WithMockUser(username="osadmin", roles = {"ADMIN","USER"}) + @Test + public void testCountTotalServiceOrdersWithState() throws Exception { + createServiceOrder(ServiceOrderStateType.INPROGRESS); + createServiceOrder(ServiceOrderStateType.ACKNOWLEDGED); + + String response = mvc.perform(MockMvcRequestBuilders.get("/tmf-api/metrics/totalServiceOrders" ) + .param("state", "ACKNOWLEDGED") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk() ) + .andReturn().getResponse().getContentAsString(); + + int totalServiceOrders = JsonPath.read(response, "$.totalServiceOrders"); + + + List serviceOrdersList = serviceOrderRepoService.findAll(); + int activeServiceOrders = (int) serviceOrdersList.stream().filter(serviceOrder -> serviceOrder.getState() == ServiceOrderStateType.ACKNOWLEDGED).count(); + + assertThat(totalServiceOrders).isEqualTo(activeServiceOrders); + assertThat(activeServiceOrders).isEqualTo(1); + } + + @WithMockUser(username="osadmin", roles = {"ADMIN","USER"}) + @Test + public void testGetTotalActiveServiceOrders() throws Exception { + createServiceOrder(ServiceOrderStateType.INPROGRESS); + createServiceOrder(ServiceOrderStateType.INPROGRESS); + createServiceOrder(ServiceOrderStateType.COMPLETED); + createServiceOrder(ServiceOrderStateType.COMPLETED); + createServiceOrder(ServiceOrderStateType.ACKNOWLEDGED); + createServiceOrder(ServiceOrderStateType.REJECTED); + + String response = mvc.perform(MockMvcRequestBuilders.get("/tmf-api/metrics/activeServiceOrders" ) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk() ) + .andReturn().getResponse().getContentAsString(); + + int totalServiceOrders = JsonPath.read(response, "$.activeServiceOrders"); + + assertThat(totalServiceOrders).isEqualTo(4); + } + + @WithMockUser(username = "osadmin", roles = {"ADMIN", "USER"}) + @Test + public void testGetServiceOrdersGroupedByDay() throws Exception { + String startTime = OffsetDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT); + + createServiceOrder(ServiceOrderStateType.INPROGRESS); + createServiceOrder(ServiceOrderStateType.INPROGRESS); + createServiceOrder(ServiceOrderStateType.INPROGRESS); + createServiceOrder(ServiceOrderStateType.ACKNOWLEDGED); + createServiceOrder(ServiceOrderStateType.ACKNOWLEDGED); + createServiceOrder(ServiceOrderStateType.PARTIAL); + + String endTime = OffsetDateTime.now(ZoneOffset.UTC).plusDays(4).format(DateTimeFormatter.ISO_INSTANT); + + String response = mvc.perform(MockMvcRequestBuilders.get("/tmf-api/metrics/serviceOrdersGroupByDay") + .param("starttime", startTime) + .param("endtime", endTime) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + + List> groupByDay = JsonPath.read(response, "$.serviceOrders.aggregations.groupByDay"); + + + OffsetDateTime start = OffsetDateTime.parse(startTime); + OffsetDateTime end = OffsetDateTime.parse(endTime); + + DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT; + + Map dayCounts = groupByDay.stream() + .collect(Collectors.toMap( + entry -> (String) entry.get("key"), + entry -> (Integer) entry.get("count") + )); + + int totalDays = (int) ChronoUnit.DAYS.between(start.toLocalDate(), end.toLocalDate()) + 1; + + for (int i = 0; i < totalDays; i++) { + OffsetDateTime day = start.plusDays(i).toLocalDate().atStartOfDay().atOffset(ZoneOffset.UTC); + String dayKey = formatter.format(day.toInstant()); + + if (i == 0) { + // Today: should have all 6 + assertThat(dayCounts.get(dayKey)).isEqualTo(6); + } else { + // Other days: should be 0 + assertThat(dayCounts.get(dayKey)).isEqualTo(0); + } + } + } + + @WithMockUser(username = "osadmin", roles = {"ADMIN", "USER"}) + @Test + public void testGetServiceOrdersGroupedByState() throws Exception { + String startTime = OffsetDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT); + + createServiceOrder(ServiceOrderStateType.INPROGRESS); + createServiceOrder(ServiceOrderStateType.INPROGRESS); + createServiceOrder(ServiceOrderStateType.INPROGRESS); + createServiceOrder(ServiceOrderStateType.ACKNOWLEDGED); + createServiceOrder(ServiceOrderStateType.ACKNOWLEDGED); + createServiceOrder(ServiceOrderStateType.PARTIAL); + + String endTime = OffsetDateTime.now(ZoneOffset.UTC).plusDays(4).format(DateTimeFormatter.ISO_INSTANT); + + String response = mvc.perform(MockMvcRequestBuilders.get("/tmf-api/metrics/serviceOrdersGroupByState") + .param("starttime", startTime) + .param("endtime", endTime) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn().getResponse().getContentAsString(); + + List> groupByState = JsonPath.read(response, "$.serviceOrders.aggregations.groupByState"); + + // Create a map from key -> count + Map stateCounts = groupByState.stream() + .collect(Collectors.toMap( + entry -> (String) entry.get("key"), + entry -> (Integer) entry.get("count") + )); + + assertThat(stateCounts.get("INPROGRESS")).isEqualTo(3); + assertThat(stateCounts.get("ACKNOWLEDGED")).isEqualTo(2); + assertThat(stateCounts.get("PARTIAL")).isEqualTo(1); + assertThat(stateCounts.get("INITIAL")).isEqualTo(0); + assertThat(stateCounts.get("REJECTED")).isEqualTo(0); + assertThat(stateCounts.get("PENDING")).isEqualTo(0); + assertThat(stateCounts.get("HELD")).isEqualTo(0); + assertThat(stateCounts.get("CANCELLED")).isEqualTo(0); + assertThat(stateCounts.get("COMPLETED")).isEqualTo(0); + assertThat(stateCounts.get("FAILED")).isEqualTo(0); + } + + private void createServiceOrder(ServiceOrderStateType stateType) throws Exception { + + ServiceOrderCreate serviceOrder = new ServiceOrderCreate(); + serviceOrder.setCategory("Test Category"); + serviceOrder.setDescription("A Test Service Order"); + serviceOrder.setRequestedStartDate(OffsetDateTime.now(ZoneOffset.UTC).toString()); + serviceOrder.setRequestedCompletionDate(OffsetDateTime.now(ZoneOffset.UTC).plusDays(3).toString()); + + String response = mvc + .perform(MockMvcRequestBuilders.post("/serviceOrdering/v4/serviceOrder") + .with( SecurityMockMvcRequestPostProcessors.csrf()) + .contentType(MediaType.APPLICATION_JSON).content(JsonUtils.toJson(serviceOrder))) + .andExpect(status().isOk()).andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andReturn().getResponse().getContentAsString(); + + ServiceOrder responseSO = JsonUtils.toJsonObj(response, ServiceOrder.class); + + // Update Service Order State + String soId = responseSO.getId(); + + ServiceOrderUpdate servOrderUpd = new ServiceOrderUpdate(); + servOrderUpd.setState(stateType); + + String response2 = mvc.perform(MockMvcRequestBuilders.patch("/serviceOrdering/v4/serviceOrder/" + soId) + .with( SecurityMockMvcRequestPostProcessors.csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content( JsonUtils.toJson( servOrderUpd ) )) + .andExpect(status().isOk() ) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andReturn().getResponse().getContentAsString(); + + ServiceOrder responsesServiceOrder2 = JsonUtils.toJsonObj(response2, ServiceOrder.class); + assertThat(responsesServiceOrder2.getState().toString()).isEqualTo(stateType.toString()); + } + +} -- GitLab From e5af2406cd729265332cba35e5e0776ce655b761 Mon Sep 17 00:00:00 2001 From: Nikos Kyriakoulis Date: Mon, 5 May 2025 14:44:33 +0300 Subject: [PATCH 2/8] starttime and endtime parameters in groupByStateBetweenDates method is compared against a ServiceOrder's orderDate --- .../org/etsi/osl/tmf/so641/repo/ServiceOrderRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/etsi/osl/tmf/so641/repo/ServiceOrderRepository.java b/src/main/java/org/etsi/osl/tmf/so641/repo/ServiceOrderRepository.java index 253e3176..b6ee5c83 100644 --- a/src/main/java/org/etsi/osl/tmf/so641/repo/ServiceOrderRepository.java +++ b/src/main/java/org/etsi/osl/tmf/so641/repo/ServiceOrderRepository.java @@ -67,7 +67,7 @@ public interface ServiceOrderRepository extends JpaRepository states); @Query("SELECT sor.state, COUNT(sor) FROM ServiceOrder sor " - + "WHERE sor.requestedStartDate >= :starttime AND sor.requestedCompletionDate <= :endtime " + + "WHERE sor.orderDate >= :starttime AND sor.orderDate <= :endtime " + "GROUP BY sor.state") List groupByStateBetweenDates(OffsetDateTime starttime, OffsetDateTime endtime); -- GitLab From 89f3185c5106273202690e79b769fac2fa557e5c Mon Sep 17 00:00:00 2001 From: Nikos Kyriakoulis Date: Tue, 6 May 2025 17:26:47 +0300 Subject: [PATCH 3/8] Removed duplicate 'tmf-api' prefix from metrics API endpoints --- .../etsi/osl/tmf/metrics/api/ServiceOrderMetricsApi.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceOrderMetricsApi.java b/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceOrderMetricsApi.java index ff31e118..22eb785f 100644 --- a/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceOrderMetricsApi.java +++ b/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceOrderMetricsApi.java @@ -29,7 +29,7 @@ public interface ServiceOrderMetricsApi { @ApiResponse(responseCode = "400", description = "Bad Request"), @ApiResponse(responseCode = "500", description = "Internal Server Error") }) - @RequestMapping(value = "/tmf-api/metrics/totalServiceOrders", method = RequestMethod.GET, produces = "application/json;charset=utf-8") + @RequestMapping(value = "/metrics/totalServiceOrders", method = RequestMethod.GET, produces = "application/json;charset=utf-8") ResponseEntity> getTotalServiceOrders( @Valid @RequestParam(value = "state", required = false) ServiceOrderStateType state ); @@ -41,7 +41,7 @@ public interface ServiceOrderMetricsApi { @ApiResponse(responseCode = "400", description = "Bad Request"), @ApiResponse(responseCode = "500", description = "Internal Server Error") }) - @RequestMapping(value = "/tmf-api/metrics/activeServiceOrders", method = RequestMethod.GET, produces = "application/json;charset=utf-8") + @RequestMapping(value = "/metrics/activeServiceOrders", method = RequestMethod.GET, produces = "application/json;charset=utf-8") ResponseEntity> getTotalActiveServiceOrders(); @@ -51,7 +51,7 @@ public interface ServiceOrderMetricsApi { @ApiResponse(responseCode = "400", description = "Bad Request"), @ApiResponse(responseCode = "500", description = "Internal Server Error") }) - @RequestMapping(value = "/tmf-api/metrics/serviceOrdersGroupByDay", method = RequestMethod.GET, produces = "application/json;charset=utf-8") + @RequestMapping(value = "/metrics/serviceOrdersGroupByDay", method = RequestMethod.GET, produces = "application/json;charset=utf-8") ResponseEntity> getServiceOrdersGroupedByDay( @Valid @RequestParam(value = "starttime", required = true) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime starttime, @Valid @RequestParam(value = "endtime", required = true) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime endtime @@ -64,7 +64,7 @@ public interface ServiceOrderMetricsApi { @ApiResponse(responseCode = "400", description = "Bad Request"), @ApiResponse(responseCode = "500", description = "Internal Server Error") }) - @RequestMapping(value = "/tmf-api/metrics/serviceOrdersGroupByState", method = RequestMethod.GET, produces = "application/json;charset=utf-8") + @RequestMapping(value = "/metrics/serviceOrdersGroupByState", method = RequestMethod.GET, produces = "application/json;charset=utf-8") ResponseEntity> getServiceOrdersGroupedByState( @Valid @RequestParam(value = "starttime", required = true) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime starttime, @Valid @RequestParam(value = "endtime", required = true) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime endtime -- GitLab From 8eb6ba5cf1532aaa660dd0f8f08c957a0830fe58 Mon Sep 17 00:00:00 2001 From: Nikos Kyriakoulis Date: Tue, 6 May 2025 17:39:08 +0300 Subject: [PATCH 4/8] Removed duplicate 'tmf-api' prefix from unit tests --- .../metrics/ServiceOrderMetricsApiControllerTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/etsi/osl/services/api/metrics/ServiceOrderMetricsApiControllerTest.java b/src/test/java/org/etsi/osl/services/api/metrics/ServiceOrderMetricsApiControllerTest.java index fcd6e5a0..757f82cd 100644 --- a/src/test/java/org/etsi/osl/services/api/metrics/ServiceOrderMetricsApiControllerTest.java +++ b/src/test/java/org/etsi/osl/services/api/metrics/ServiceOrderMetricsApiControllerTest.java @@ -70,7 +70,7 @@ public class ServiceOrderMetricsApiControllerTest { public void testCountTotalServiceOrders() throws Exception { createServiceOrder(ServiceOrderStateType.INPROGRESS); - String response = mvc.perform(MockMvcRequestBuilders.get("/tmf-api/metrics/totalServiceOrders" ) + String response = mvc.perform(MockMvcRequestBuilders.get("/metrics/totalServiceOrders" ) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk() ) .andReturn().getResponse().getContentAsString(); @@ -87,7 +87,7 @@ public class ServiceOrderMetricsApiControllerTest { createServiceOrder(ServiceOrderStateType.INPROGRESS); createServiceOrder(ServiceOrderStateType.ACKNOWLEDGED); - String response = mvc.perform(MockMvcRequestBuilders.get("/tmf-api/metrics/totalServiceOrders" ) + String response = mvc.perform(MockMvcRequestBuilders.get("/metrics/totalServiceOrders" ) .param("state", "ACKNOWLEDGED") .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk() ) @@ -113,7 +113,7 @@ public class ServiceOrderMetricsApiControllerTest { createServiceOrder(ServiceOrderStateType.ACKNOWLEDGED); createServiceOrder(ServiceOrderStateType.REJECTED); - String response = mvc.perform(MockMvcRequestBuilders.get("/tmf-api/metrics/activeServiceOrders" ) + String response = mvc.perform(MockMvcRequestBuilders.get("/metrics/activeServiceOrders" ) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk() ) .andReturn().getResponse().getContentAsString(); @@ -137,7 +137,7 @@ public class ServiceOrderMetricsApiControllerTest { String endTime = OffsetDateTime.now(ZoneOffset.UTC).plusDays(4).format(DateTimeFormatter.ISO_INSTANT); - String response = mvc.perform(MockMvcRequestBuilders.get("/tmf-api/metrics/serviceOrdersGroupByDay") + String response = mvc.perform(MockMvcRequestBuilders.get("/metrics/serviceOrdersGroupByDay") .param("starttime", startTime) .param("endtime", endTime) .contentType(MediaType.APPLICATION_JSON)) @@ -188,7 +188,7 @@ public class ServiceOrderMetricsApiControllerTest { String endTime = OffsetDateTime.now(ZoneOffset.UTC).plusDays(4).format(DateTimeFormatter.ISO_INSTANT); - String response = mvc.perform(MockMvcRequestBuilders.get("/tmf-api/metrics/serviceOrdersGroupByState") + String response = mvc.perform(MockMvcRequestBuilders.get("/metrics/serviceOrdersGroupByState") .param("starttime", startTime) .param("endtime", endTime) .contentType(MediaType.APPLICATION_JSON)) -- GitLab From 91af6a7daf6758da0c945717b40d7a8d5c5a3aa7 Mon Sep 17 00:00:00 2001 From: Nikos Kyriakoulis Date: Wed, 7 May 2025 16:28:55 +0300 Subject: [PATCH 5/8] Changed structure to gather all metrics endpoints under one Controller --- .../{ServiceOrderMetricsApi.java => MetricsApi.java} | 7 +++---- ...icsApiController.java => MetricsApiController.java} | 10 +++++----- ...MetricsRepoService.java => MetricsRepoService.java} | 2 +- ...ntrollerTest.java => MetricsApiControllerTest.java} | 2 +- 4 files changed, 10 insertions(+), 11 deletions(-) rename src/main/java/org/etsi/osl/tmf/metrics/api/{ServiceOrderMetricsApi.java => MetricsApi.java} (94%) rename src/main/java/org/etsi/osl/tmf/metrics/api/{ServiceOrderMetricsApiController.java => MetricsApiController.java} (92%) rename src/main/java/org/etsi/osl/tmf/metrics/reposervices/{ServiceOrderMetricsRepoService.java => MetricsRepoService.java} (98%) rename src/test/java/org/etsi/osl/services/api/metrics/{ServiceOrderMetricsApiControllerTest.java => MetricsApiControllerTest.java} (99%) diff --git a/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceOrderMetricsApi.java b/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApi.java similarity index 94% rename from src/main/java/org/etsi/osl/tmf/metrics/api/ServiceOrderMetricsApi.java rename to src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApi.java index 22eb785f..421902df 100644 --- a/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceOrderMetricsApi.java +++ b/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApi.java @@ -5,7 +5,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; -import org.etsi.osl.tmf.common.model.service.ServiceStateType; import org.etsi.osl.tmf.so641.model.ServiceOrderStateType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,10 +17,10 @@ import org.springframework.web.bind.annotation.RequestParam; import java.time.OffsetDateTime; import java.util.Map; -@Tag(name = "ServiceOrderMetricsApi") -public interface ServiceOrderMetricsApi { +@Tag(name = "MetricsApi") +public interface MetricsApi { - Logger log = LoggerFactory.getLogger(ServiceOrderMetricsApi.class); + Logger log = LoggerFactory.getLogger(MetricsApi.class); @Operation(summary = "Get total number of service orders", operationId = "getTotalServiceOrders") @ApiResponses(value = { diff --git a/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceOrderMetricsApiController.java b/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java similarity index 92% rename from src/main/java/org/etsi/osl/tmf/metrics/api/ServiceOrderMetricsApiController.java rename to src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java index e337e28f..9b2afdba 100644 --- a/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceOrderMetricsApiController.java +++ b/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java @@ -1,6 +1,6 @@ package org.etsi.osl.tmf.metrics.api; -import org.etsi.osl.tmf.metrics.reposervices.ServiceOrderMetricsRepoService; +import org.etsi.osl.tmf.metrics.reposervices.MetricsRepoService; import org.etsi.osl.tmf.so641.model.ServiceOrderStateType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,13 +18,13 @@ import java.util.Map; @Controller -public class ServiceOrderMetricsApiController implements ServiceOrderMetricsApi{ +public class MetricsApiController implements MetricsApi { - private static final Logger log = LoggerFactory.getLogger(ServiceOrderMetricsApiController.class); - private final ServiceOrderMetricsRepoService serviceOrderMetricsRepoService; + private static final Logger log = LoggerFactory.getLogger(MetricsApiController.class); + private final MetricsRepoService serviceOrderMetricsRepoService; @Autowired - public ServiceOrderMetricsApiController(ServiceOrderMetricsRepoService serviceOrderMetricsRepoService) { + public MetricsApiController(MetricsRepoService serviceOrderMetricsRepoService) { this.serviceOrderMetricsRepoService = serviceOrderMetricsRepoService; } diff --git a/src/main/java/org/etsi/osl/tmf/metrics/reposervices/ServiceOrderMetricsRepoService.java b/src/main/java/org/etsi/osl/tmf/metrics/reposervices/MetricsRepoService.java similarity index 98% rename from src/main/java/org/etsi/osl/tmf/metrics/reposervices/ServiceOrderMetricsRepoService.java rename to src/main/java/org/etsi/osl/tmf/metrics/reposervices/MetricsRepoService.java index 508d9a63..0276132f 100644 --- a/src/main/java/org/etsi/osl/tmf/metrics/reposervices/ServiceOrderMetricsRepoService.java +++ b/src/main/java/org/etsi/osl/tmf/metrics/reposervices/MetricsRepoService.java @@ -13,7 +13,7 @@ import java.util.Map; import java.util.stream.Collectors; @Service -public class ServiceOrderMetricsRepoService { +public class MetricsRepoService { @Autowired ObjectMapper objectMapper; diff --git a/src/test/java/org/etsi/osl/services/api/metrics/ServiceOrderMetricsApiControllerTest.java b/src/test/java/org/etsi/osl/services/api/metrics/MetricsApiControllerTest.java similarity index 99% rename from src/test/java/org/etsi/osl/services/api/metrics/ServiceOrderMetricsApiControllerTest.java rename to src/test/java/org/etsi/osl/services/api/metrics/MetricsApiControllerTest.java index 757f82cd..c20da399 100644 --- a/src/test/java/org/etsi/osl/services/api/metrics/ServiceOrderMetricsApiControllerTest.java +++ b/src/test/java/org/etsi/osl/services/api/metrics/MetricsApiControllerTest.java @@ -46,7 +46,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @ActiveProfiles("testing") //@TestPropertySource( // locations = "classpath:application-testing.yml") -public class ServiceOrderMetricsApiControllerTest { +public class MetricsApiControllerTest { @Autowired private MockMvc mvc; -- GitLab From 95baeb32ba646c1228cec813377bb35da0169020 Mon Sep 17 00:00:00 2001 From: Nikos Kyriakoulis Date: Mon, 12 May 2025 18:14:48 +0300 Subject: [PATCH 6/8] Use metrics models for API responses --- .../etsi/osl/tmf/metrics/api/MetricsApi.java | 12 ++-- .../tmf/metrics/api/MetricsApiController.java | 66 +++++++------------ .../reposervices/MetricsRepoService.java | 13 ++-- 3 files changed, 42 insertions(+), 49 deletions(-) diff --git a/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApi.java b/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApi.java index 421902df..a7ef65fb 100644 --- a/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApi.java +++ b/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApi.java @@ -5,6 +5,10 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import org.etsi.osl.tmf.metrics.ActiveServiceOrders; +import org.etsi.osl.tmf.metrics.ServiceOrdersGroupByDay; +import org.etsi.osl.tmf.metrics.ServiceOrdersGroupByState; +import org.etsi.osl.tmf.metrics.TotalServiceOrders; import org.etsi.osl.tmf.so641.model.ServiceOrderStateType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,7 +33,7 @@ public interface MetricsApi { @ApiResponse(responseCode = "500", description = "Internal Server Error") }) @RequestMapping(value = "/metrics/totalServiceOrders", method = RequestMethod.GET, produces = "application/json;charset=utf-8") - ResponseEntity> getTotalServiceOrders( + ResponseEntity getTotalServiceOrders( @Valid @RequestParam(value = "state", required = false) ServiceOrderStateType state ); @@ -41,7 +45,7 @@ public interface MetricsApi { @ApiResponse(responseCode = "500", description = "Internal Server Error") }) @RequestMapping(value = "/metrics/activeServiceOrders", method = RequestMethod.GET, produces = "application/json;charset=utf-8") - ResponseEntity> getTotalActiveServiceOrders(); + ResponseEntity getTotalActiveServiceOrders(); @Operation(summary = "Get service orders grouped by day", operationId = "getServiceOrdersGroupedByDay") @@ -51,7 +55,7 @@ public interface MetricsApi { @ApiResponse(responseCode = "500", description = "Internal Server Error") }) @RequestMapping(value = "/metrics/serviceOrdersGroupByDay", method = RequestMethod.GET, produces = "application/json;charset=utf-8") - ResponseEntity> getServiceOrdersGroupedByDay( + ResponseEntity getServiceOrdersGroupedByDay( @Valid @RequestParam(value = "starttime", required = true) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime starttime, @Valid @RequestParam(value = "endtime", required = true) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime endtime ); @@ -64,7 +68,7 @@ public interface MetricsApi { @ApiResponse(responseCode = "500", description = "Internal Server Error") }) @RequestMapping(value = "/metrics/serviceOrdersGroupByState", method = RequestMethod.GET, produces = "application/json;charset=utf-8") - ResponseEntity> getServiceOrdersGroupedByState( + ResponseEntity getServiceOrdersGroupedByState( @Valid @RequestParam(value = "starttime", required = true) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime starttime, @Valid @RequestParam(value = "endtime", required = true) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime endtime ); diff --git a/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java b/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java index 9b2afdba..1ec4b706 100644 --- a/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java +++ b/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java @@ -1,5 +1,6 @@ package org.etsi.osl.tmf.metrics.api; +import org.etsi.osl.tmf.metrics.*; import org.etsi.osl.tmf.metrics.reposervices.MetricsRepoService; import org.etsi.osl.tmf.so641.model.ServiceOrderStateType; import org.slf4j.Logger; @@ -29,11 +30,10 @@ public class MetricsApiController implements MetricsApi { } @Override - public ResponseEntity> getTotalServiceOrders(ServiceOrderStateType state) { + public ResponseEntity getTotalServiceOrders(ServiceOrderStateType state) { try { int totalServiceOrders = serviceOrderMetricsRepoService.countTotalServiceOrders(state); - Map response = new HashMap<>(); - response.put("totalServiceOrders", totalServiceOrders); + TotalServiceOrders response = new TotalServiceOrders(totalServiceOrders); return new ResponseEntity<>(response, HttpStatus.OK); } catch (Exception e) { log.error("Couldn't retrieve total service orders. ", e); @@ -42,11 +42,10 @@ public class MetricsApiController implements MetricsApi { } @Override - public ResponseEntity> getTotalActiveServiceOrders() { + public ResponseEntity getTotalActiveServiceOrders() { try { int totalActiveServiceOrders = serviceOrderMetricsRepoService.countTotalActiveServiceOrders(); - Map response = new HashMap<>(); - response.put("activeServiceOrders", totalActiveServiceOrders); + ActiveServiceOrders response = new ActiveServiceOrders(totalActiveServiceOrders); return new ResponseEntity<>(response, HttpStatus.OK); } catch (Exception e) { log.error("Couldn't retrieve total active service orders. ", e); @@ -55,36 +54,29 @@ public class MetricsApiController implements MetricsApi { } @Override - public ResponseEntity> getServiceOrdersGroupedByDay(OffsetDateTime starttime, OffsetDateTime endtime) { + public ResponseEntity getServiceOrdersGroupedByDay(OffsetDateTime starttime, OffsetDateTime endtime) { try { - Map orderDatesGroupedByDate = serviceOrderMetricsRepoService.getServiceOrdersGroupedByDay(starttime, endtime); + Map orderDatesGroupedByDate = serviceOrderMetricsRepoService.getServiceOrdersGroupedByDay(starttime, endtime); // Fill missing days with count 0 - Map fullDayMap = new LinkedHashMap<>(); + Map fullDayMap = new LinkedHashMap<>(); OffsetDateTime cursor = starttime.truncatedTo(ChronoUnit.DAYS); OffsetDateTime endDay = endtime.truncatedTo(ChronoUnit.DAYS); while (!cursor.isAfter(endDay)) { String key = cursor.toInstant().toString(); - fullDayMap.put(key, orderDatesGroupedByDate.getOrDefault(key, 0L)); + fullDayMap.put(key, orderDatesGroupedByDate.getOrDefault(key, 0)); cursor = cursor.plusDays(1); } - List> groupByDayList = fullDayMap.entrySet().stream() - .map(entry -> { - Map dayMap = new HashMap<>(); - dayMap.put("key", entry.getKey()); - dayMap.put("count", entry.getValue()); - return dayMap; - }) + // Convert to model list + List groupByDayList = fullDayMap.entrySet().stream() + .map(entry -> new GroupByItem(entry.getKey(), entry.getValue())) .toList(); - Map aggregations = Map.of("groupByDay", groupByDayList); - Map serviceOrders = Map.of( - "total", fullDayMap.values().stream().mapToLong(Long::longValue).sum(), - "aggregations", aggregations - ); - - Map response = Map.of("serviceOrders", serviceOrders); + GroupByDayAggregations aggregations = new GroupByDayAggregations(groupByDayList); + int total = fullDayMap.values().stream().mapToInt(Integer::intValue).sum(); + ServiceOrdersDay wrapper = new ServiceOrdersDay(total, aggregations); + ServiceOrdersGroupByDay response = new ServiceOrdersGroupByDay(wrapper); return new ResponseEntity<>(response, HttpStatus.OK); @@ -95,7 +87,7 @@ public class MetricsApiController implements MetricsApi { } @Override - public ResponseEntity> getServiceOrdersGroupedByState(OffsetDateTime starttime, OffsetDateTime endtime) { + public ResponseEntity getServiceOrdersGroupedByState(OffsetDateTime starttime, OffsetDateTime endtime) { try { Map servicesByState = serviceOrderMetricsRepoService.getServiceOrdersGroupedByState(starttime, endtime); @@ -110,25 +102,17 @@ public class MetricsApiController implements MetricsApi { fullStateMap.put(key.toUpperCase(), value); }); - // Build groupByState list - List> groupByStateList = fullStateMap.entrySet().stream() - .map(entry -> { - Map map = new HashMap<>(); - map.put("key", entry.getKey()); - map.put("count", entry.getValue()); - return map; - }) + // Create aggregation items + List groupByStateList = fullStateMap.entrySet().stream() + .map(entry -> new GroupByItem(entry.getKey(), entry.getValue())) .toList(); + // Build response structure using models + GroupByStateAggregations aggregations = new GroupByStateAggregations(groupByStateList); + int total = fullStateMap.values().stream().mapToInt(Integer::intValue).sum(); + ServiceOrders services = new ServiceOrders(total, aggregations); + ServiceOrdersGroupByState response = new ServiceOrdersGroupByState(services); - // Wrap in response structure - Map aggregations = Map.of("groupByState", groupByStateList); - Map services = Map.of( - "total", fullStateMap.values().stream().mapToInt(Integer::intValue).sum(), - "aggregations", aggregations - ); - - Map response = Map.of("serviceOrders", services); return new ResponseEntity<>(response, HttpStatus.OK); } catch (Exception e) { diff --git a/src/main/java/org/etsi/osl/tmf/metrics/reposervices/MetricsRepoService.java b/src/main/java/org/etsi/osl/tmf/metrics/reposervices/MetricsRepoService.java index 0276132f..990868f8 100644 --- a/src/main/java/org/etsi/osl/tmf/metrics/reposervices/MetricsRepoService.java +++ b/src/main/java/org/etsi/osl/tmf/metrics/reposervices/MetricsRepoService.java @@ -39,22 +39,27 @@ public class MetricsRepoService { return serviceOrderRepository.countAllActive(currentDate, activeStates); } - public Map getServiceOrdersGroupedByDay(OffsetDateTime starttime, OffsetDateTime endtime) { + public Map getServiceOrdersGroupedByDay(OffsetDateTime starttime, OffsetDateTime endtime) { if (starttime.plusDays(31).isBefore(endtime)) { starttime = endtime.minusDays(31); } List orderDates = serviceOrderRepository.getOrderDatesBetweenDates(starttime, endtime); - return orderDates.stream() + // First group by day with count as Long + Map grouped = orderDates.stream() .map(dt -> dt.truncatedTo(ChronoUnit.DAYS)) // Remove time portion .collect(Collectors.groupingBy( dt -> dt.toInstant().toString(), // Format as ISO string (Z) Collectors.counting() )); - - + // Convert Map to Map + return grouped.entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + e -> e.getValue().intValue() + )); } public Map getServiceOrdersGroupedByState(OffsetDateTime starttime, OffsetDateTime endtime) { -- GitLab From 4e80e14ef6b816901ce90110ee1fb05fcbeb53dd Mon Sep 17 00:00:00 2001 From: Nikos Kyriakoulis Date: Wed, 14 May 2025 13:17:00 +0300 Subject: [PATCH 7/8] Use new version of metrics model --- .../tmf/metrics/api/MetricsApiController.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java b/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java index 1ec4b706..d964b5fe 100644 --- a/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java +++ b/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java @@ -12,7 +12,6 @@ import org.springframework.stereotype.Controller; import java.time.OffsetDateTime; import java.time.temporal.ChronoUnit; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -69,13 +68,13 @@ public class MetricsApiController implements MetricsApi { } // Convert to model list - List groupByDayList = fullDayMap.entrySet().stream() - .map(entry -> new GroupByItem(entry.getKey(), entry.getValue())) + List groupByDayList = fullDayMap.entrySet().stream() + .map(entry -> new ServiceOrdersGroupByDayItem(entry.getKey(), entry.getValue())) .toList(); - GroupByDayAggregations aggregations = new GroupByDayAggregations(groupByDayList); + ServiceOrdersGroupByDayAggregations aggregations = new ServiceOrdersGroupByDayAggregations(groupByDayList); int total = fullDayMap.values().stream().mapToInt(Integer::intValue).sum(); - ServiceOrdersDay wrapper = new ServiceOrdersDay(total, aggregations); + ServiceOrdersGroupByDayParent wrapper = new ServiceOrdersGroupByDayParent(total, aggregations); ServiceOrdersGroupByDay response = new ServiceOrdersGroupByDay(wrapper); return new ResponseEntity<>(response, HttpStatus.OK); @@ -103,14 +102,14 @@ public class MetricsApiController implements MetricsApi { }); // Create aggregation items - List groupByStateList = fullStateMap.entrySet().stream() - .map(entry -> new GroupByItem(entry.getKey(), entry.getValue())) + List groupByStateList = fullStateMap.entrySet().stream() + .map(entry -> new ServiceOrdersGroupByStateItem(ServiceOrderStateType.valueOf(entry.getKey()), entry.getValue())) .toList(); // Build response structure using models - GroupByStateAggregations aggregations = new GroupByStateAggregations(groupByStateList); + ServiceOrdersGroupByStateAggregations aggregations = new ServiceOrdersGroupByStateAggregations(groupByStateList); int total = fullStateMap.values().stream().mapToInt(Integer::intValue).sum(); - ServiceOrders services = new ServiceOrders(total, aggregations); + ServiceOrdersGroupByStateParent services = new ServiceOrdersGroupByStateParent(total, aggregations); ServiceOrdersGroupByState response = new ServiceOrdersGroupByState(services); return new ResponseEntity<>(response, HttpStatus.OK); -- GitLab From 7d3d261a263595d3abc4e24640efc643085b4e4d Mon Sep 17 00:00:00 2001 From: Kostis Trantzas Date: Fri, 16 May 2025 01:37:40 +0300 Subject: [PATCH 8/8] renaming files --- .../{MetricsApi.java => ServiceOrderMetricsApi.java} | 4 ++-- ...ller.java => ServiceOrderMetricsApiController.java} | 10 +++++----- ...ervice.java => ServiceOrderMetricsRepoService.java} | 2 +- ....java => ServiceOrderMetricsApiControllerTest.java} | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) rename src/main/java/org/etsi/osl/tmf/metrics/api/{MetricsApi.java => ServiceOrderMetricsApi.java} (97%) rename src/main/java/org/etsi/osl/tmf/metrics/api/{MetricsApiController.java => ServiceOrderMetricsApiController.java} (92%) rename src/main/java/org/etsi/osl/tmf/metrics/reposervices/{MetricsRepoService.java => ServiceOrderMetricsRepoService.java} (98%) rename src/test/java/org/etsi/osl/services/api/metrics/{MetricsApiControllerTest.java => ServiceOrderMetricsApiControllerTest.java} (99%) diff --git a/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApi.java b/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceOrderMetricsApi.java similarity index 97% rename from src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApi.java rename to src/main/java/org/etsi/osl/tmf/metrics/api/ServiceOrderMetricsApi.java index a7ef65fb..07a9252f 100644 --- a/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApi.java +++ b/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceOrderMetricsApi.java @@ -22,9 +22,9 @@ import java.time.OffsetDateTime; import java.util.Map; @Tag(name = "MetricsApi") -public interface MetricsApi { +public interface ServiceOrderMetricsApi { - Logger log = LoggerFactory.getLogger(MetricsApi.class); + Logger log = LoggerFactory.getLogger(ServiceOrderMetricsApi.class); @Operation(summary = "Get total number of service orders", operationId = "getTotalServiceOrders") @ApiResponses(value = { diff --git a/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java b/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceOrderMetricsApiController.java similarity index 92% rename from src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java rename to src/main/java/org/etsi/osl/tmf/metrics/api/ServiceOrderMetricsApiController.java index d964b5fe..6b0a1bf8 100644 --- a/src/main/java/org/etsi/osl/tmf/metrics/api/MetricsApiController.java +++ b/src/main/java/org/etsi/osl/tmf/metrics/api/ServiceOrderMetricsApiController.java @@ -1,7 +1,7 @@ package org.etsi.osl.tmf.metrics.api; import org.etsi.osl.tmf.metrics.*; -import org.etsi.osl.tmf.metrics.reposervices.MetricsRepoService; +import org.etsi.osl.tmf.metrics.reposervices.ServiceOrderMetricsRepoService; import org.etsi.osl.tmf.so641.model.ServiceOrderStateType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,13 +18,13 @@ import java.util.Map; @Controller -public class MetricsApiController implements MetricsApi { +public class ServiceOrderMetricsApiController implements ServiceOrderMetricsApi { - private static final Logger log = LoggerFactory.getLogger(MetricsApiController.class); - private final MetricsRepoService serviceOrderMetricsRepoService; + private static final Logger log = LoggerFactory.getLogger(ServiceOrderMetricsApiController.class); + private final ServiceOrderMetricsRepoService serviceOrderMetricsRepoService; @Autowired - public MetricsApiController(MetricsRepoService serviceOrderMetricsRepoService) { + public ServiceOrderMetricsApiController(ServiceOrderMetricsRepoService serviceOrderMetricsRepoService) { this.serviceOrderMetricsRepoService = serviceOrderMetricsRepoService; } diff --git a/src/main/java/org/etsi/osl/tmf/metrics/reposervices/MetricsRepoService.java b/src/main/java/org/etsi/osl/tmf/metrics/reposervices/ServiceOrderMetricsRepoService.java similarity index 98% rename from src/main/java/org/etsi/osl/tmf/metrics/reposervices/MetricsRepoService.java rename to src/main/java/org/etsi/osl/tmf/metrics/reposervices/ServiceOrderMetricsRepoService.java index 990868f8..9bd49793 100644 --- a/src/main/java/org/etsi/osl/tmf/metrics/reposervices/MetricsRepoService.java +++ b/src/main/java/org/etsi/osl/tmf/metrics/reposervices/ServiceOrderMetricsRepoService.java @@ -13,7 +13,7 @@ import java.util.Map; import java.util.stream.Collectors; @Service -public class MetricsRepoService { +public class ServiceOrderMetricsRepoService { @Autowired ObjectMapper objectMapper; diff --git a/src/test/java/org/etsi/osl/services/api/metrics/MetricsApiControllerTest.java b/src/test/java/org/etsi/osl/services/api/metrics/ServiceOrderMetricsApiControllerTest.java similarity index 99% rename from src/test/java/org/etsi/osl/services/api/metrics/MetricsApiControllerTest.java rename to src/test/java/org/etsi/osl/services/api/metrics/ServiceOrderMetricsApiControllerTest.java index c20da399..757f82cd 100644 --- a/src/test/java/org/etsi/osl/services/api/metrics/MetricsApiControllerTest.java +++ b/src/test/java/org/etsi/osl/services/api/metrics/ServiceOrderMetricsApiControllerTest.java @@ -46,7 +46,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @ActiveProfiles("testing") //@TestPropertySource( // locations = "classpath:application-testing.yml") -public class MetricsApiControllerTest { +public class ServiceOrderMetricsApiControllerTest { @Autowired private MockMvc mvc; -- GitLab