From ee047b23aab92e490a64319ed6e71bc61d4c9177 Mon Sep 17 00:00:00 2001 From: jaroslawmalekcodete Date: Tue, 5 Nov 2024 08:43:53 +0100 Subject: [PATCH 1/5] feat(sdk): kraken-29 - add new endpoint for quick start guide (#62) --- .../controller/api/StartGuideController.java | 31 ++ .../dto/start/ApiMappingInfoDto.java | 13 + .../dto/start/DeploymentInfoDto.java | 14 + .../SellerApiServerRegistrationInfoDto.java | 13 + .../dto/start/StartGuideInfoDto.java | 14 + .../service/start/StartGuideService.java | 107 ++++++ .../api/StartGuideControllerTest.java | 69 ++++ .../service/start/StartGuideServiceTest.java | 348 ++++++++++++++++++ .../core/repo/UnifiedAssetRepository.java | 2 + 9 files changed, 611 insertions(+) create mode 100644 kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/api/StartGuideController.java create mode 100644 kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/start/ApiMappingInfoDto.java create mode 100644 kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/start/DeploymentInfoDto.java create mode 100644 kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/start/SellerApiServerRegistrationInfoDto.java create mode 100644 kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/start/StartGuideInfoDto.java create mode 100644 kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/service/start/StartGuideService.java create mode 100644 kraken-java-sdk/kraken-java-sdk-controller/src/test/java/com/consoleconnect/kraken/operator/controller/api/StartGuideControllerTest.java create mode 100644 kraken-java-sdk/kraken-java-sdk-controller/src/test/java/com/consoleconnect/kraken/operator/controller/service/start/StartGuideServiceTest.java diff --git a/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/api/StartGuideController.java b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/api/StartGuideController.java new file mode 100644 index 00000000..44c9fe2b --- /dev/null +++ b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/api/StartGuideController.java @@ -0,0 +1,31 @@ +package com.consoleconnect.kraken.operator.controller.api; + +import com.consoleconnect.kraken.operator.controller.dto.start.StartGuideInfoDto; +import com.consoleconnect.kraken.operator.controller.service.start.StartGuideService; +import com.consoleconnect.kraken.operator.core.model.HttpResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(value = StartGuideController.URL, produces = MediaType.APPLICATION_JSON_VALUE) +@RequiredArgsConstructor +@Tag(name = "Start Guide APIs", description = "Portal APIs") +public class StartGuideController { + + public static final String URL = "/start/guide"; + private final StartGuideService service; + + @Operation(summary = "Get start guide info") + @GetMapping("/{productId}") + public HttpResponse getStartGuideInfo( + @PathVariable("productId") String productId, @RequestParam(value = "kind") String kind) { + return HttpResponse.ok(service.getStartGuideInfo(productId, kind)); + } +} diff --git a/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/start/ApiMappingInfoDto.java b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/start/ApiMappingInfoDto.java new file mode 100644 index 00000000..3b8f11c7 --- /dev/null +++ b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/start/ApiMappingInfoDto.java @@ -0,0 +1,13 @@ +package com.consoleconnect.kraken.operator.controller.dto.start; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ApiMappingInfoDto { + + private Boolean atLeastOneMappingCompleted; +} diff --git a/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/start/DeploymentInfoDto.java b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/start/DeploymentInfoDto.java new file mode 100644 index 00000000..3191675d --- /dev/null +++ b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/start/DeploymentInfoDto.java @@ -0,0 +1,14 @@ +package com.consoleconnect.kraken.operator.controller.dto.start; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class DeploymentInfoDto { + private Boolean atLeastOneApiDeployedToStage; + private Boolean atLeastOneBuyerRegistered; + private Boolean atLeastOneApiDeployedToProduction; +} diff --git a/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/start/SellerApiServerRegistrationInfoDto.java b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/start/SellerApiServerRegistrationInfoDto.java new file mode 100644 index 00000000..069d1855 --- /dev/null +++ b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/start/SellerApiServerRegistrationInfoDto.java @@ -0,0 +1,13 @@ +package com.consoleconnect.kraken.operator.controller.dto.start; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SellerApiServerRegistrationInfoDto { + + private Boolean atLeastOneSellerApiRegistered; +} diff --git a/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/start/StartGuideInfoDto.java b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/start/StartGuideInfoDto.java new file mode 100644 index 00000000..fde11c58 --- /dev/null +++ b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/start/StartGuideInfoDto.java @@ -0,0 +1,14 @@ +package com.consoleconnect.kraken.operator.controller.dto.start; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class StartGuideInfoDto { + private SellerApiServerRegistrationInfoDto sellerApiServerRegistrationInfo; + private ApiMappingInfoDto apiMappingInfo; + private DeploymentInfoDto deploymentInfo; +} diff --git a/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/service/start/StartGuideService.java b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/service/start/StartGuideService.java new file mode 100644 index 00000000..23294271 --- /dev/null +++ b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/service/start/StartGuideService.java @@ -0,0 +1,107 @@ +package com.consoleconnect.kraken.operator.controller.service.start; + +import static com.consoleconnect.kraken.operator.core.enums.AssetKindEnum.PRODUCT_BUYER; +import static com.consoleconnect.kraken.operator.core.enums.MappingStatusEnum.COMPLETE; +import static com.consoleconnect.kraken.operator.core.service.UnifiedAssetService.getSearchPageRequest; + +import com.consoleconnect.kraken.operator.controller.dto.start.ApiMappingInfoDto; +import com.consoleconnect.kraken.operator.controller.dto.start.DeploymentInfoDto; +import com.consoleconnect.kraken.operator.controller.dto.start.SellerApiServerRegistrationInfoDto; +import com.consoleconnect.kraken.operator.controller.dto.start.StartGuideInfoDto; +import com.consoleconnect.kraken.operator.controller.model.Environment; +import com.consoleconnect.kraken.operator.controller.service.ApiComponentService; +import com.consoleconnect.kraken.operator.controller.service.EnvironmentService; +import com.consoleconnect.kraken.operator.controller.service.ProductDeploymentService; +import com.consoleconnect.kraken.operator.core.enums.DeployStatusEnum; +import com.consoleconnect.kraken.operator.core.enums.EnvNameEnum; +import com.consoleconnect.kraken.operator.core.exception.KrakenException; +import com.consoleconnect.kraken.operator.core.repo.UnifiedAssetRepository; +import com.consoleconnect.kraken.operator.core.service.UnifiedAssetService; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +public class StartGuideService { + + public static final int DEFAULT_NUMBER_OF_COMPLETED_MAPPINGS = 2; + public static final String PRODUCT_ID_CAN_NOT_BE_EMPTY_ERROR = "Product id can not be empty."; + public static final String KIND_CAN_NOT_BE_EMPTY_ERROR = "Kind can not be empty."; + + private final ApiComponentService apiComponentService; + private final UnifiedAssetRepository unifiedAssetRepository; + private final UnifiedAssetService unifiedAssetService; + private final EnvironmentService environmentService; + private final ProductDeploymentService productDeploymentService; + + public StartGuideInfoDto getStartGuideInfo(String productId, String kind) { + validateParams(productId, kind); + var atLeastOneSellerApiRegistered = atLeastOneSellerApiRegistered(productId, kind); + var atLeastOneMappingCompleted = atLeastOneMappingCompleted(); + var atLestOnBuyerRegistered = atLestOnBuyerRegistered(productId); + var atLeastOneApiDeployedOnStage = atLeastOneApiDeployedToEvn(productId, EnvNameEnum.STAGE); + var atLeastOneApiDeployedOnProduction = + atLeastOneApiDeployedToEvn(productId, EnvNameEnum.PRODUCTION); + + return new StartGuideInfoDto( + new SellerApiServerRegistrationInfoDto(atLeastOneSellerApiRegistered), + new ApiMappingInfoDto(atLeastOneMappingCompleted), + new DeploymentInfoDto( + atLeastOneApiDeployedOnStage, + atLestOnBuyerRegistered, + atLeastOneApiDeployedOnProduction)); + } + + private void validateParams(String productId, String kind) { + if (StringUtils.isBlank(productId)) { + throw new KrakenException(400, PRODUCT_ID_CAN_NOT_BE_EMPTY_ERROR); + } + if (StringUtils.isBlank(kind)) { + throw new KrakenException(400, KIND_CAN_NOT_BE_EMPTY_ERROR); + } + } + + private boolean atLeastOneSellerApiRegistered(String productId, String kind) { + var parentId = unifiedAssetService.findOne(productId).getId(); + return unifiedAssetRepository.existsByParentIdAndKind(parentId, kind); + } + + private boolean atLeastOneMappingCompleted() { + var completedMappings = + apiComponentService.listAllApiUseCase().stream() + .flatMap(component -> component.getDetails().stream()) + .filter(mapping -> mapping.getMappingStatus().equalsIgnoreCase(COMPLETE.getDesc())) + .toList(); + return completedMappings.size() > DEFAULT_NUMBER_OF_COMPLETED_MAPPINGS; + } + + private boolean atLestOnBuyerRegistered(String productId) { + var parentId = unifiedAssetService.findOneByIdOrKey(productId).getId().toString(); + return unifiedAssetRepository.existsByParentIdAndKind(parentId, PRODUCT_BUYER.getKind()); + } + + private boolean atLeastOneApiDeployedToEvn(String productId, EnvNameEnum envNameEnum) { + var environments = + environmentService + .search(productId, getSearchPageRequest(0, EnvNameEnum.values().length)) + .getData(); + Optional environment = + environments.stream() + .filter(e -> envNameEnum.name().equalsIgnoreCase(e.getName())) + .findFirst(); + return environment + .map( + env -> { + var deployments = + productDeploymentService.retrieveApiMapperDeployments( + environment.get().getId(), + null, + DeployStatusEnum.SUCCESS, + getSearchPageRequest(0, 1)); + return !deployments.getData().isEmpty(); + }) + .orElse(false); + } +} diff --git a/kraken-java-sdk/kraken-java-sdk-controller/src/test/java/com/consoleconnect/kraken/operator/controller/api/StartGuideControllerTest.java b/kraken-java-sdk/kraken-java-sdk-controller/src/test/java/com/consoleconnect/kraken/operator/controller/api/StartGuideControllerTest.java new file mode 100644 index 00000000..44c23cba --- /dev/null +++ b/kraken-java-sdk/kraken-java-sdk-controller/src/test/java/com/consoleconnect/kraken/operator/controller/api/StartGuideControllerTest.java @@ -0,0 +1,69 @@ +package com.consoleconnect.kraken.operator.controller.api; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import com.consoleconnect.kraken.operator.config.TestApplication; +import com.consoleconnect.kraken.operator.controller.WebTestClientHelper; +import com.consoleconnect.kraken.operator.controller.dto.start.ApiMappingInfoDto; +import com.consoleconnect.kraken.operator.controller.dto.start.DeploymentInfoDto; +import com.consoleconnect.kraken.operator.controller.dto.start.SellerApiServerRegistrationInfoDto; +import com.consoleconnect.kraken.operator.controller.dto.start.StartGuideInfoDto; +import com.consoleconnect.kraken.operator.controller.service.start.StartGuideService; +import com.consoleconnect.kraken.operator.core.model.HttpResponse; +import com.consoleconnect.kraken.operator.test.AbstractIntegrationTest; +import com.consoleconnect.kraken.operator.test.MockIntegrationTest; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.web.reactive.server.WebTestClient; + +@ActiveProfiles("test-auth-server-enabled") +@MockIntegrationTest +@ContextConfiguration(classes = TestApplication.class) +class StartGuideControllerTest extends AbstractIntegrationTest { + + @Autowired protected ObjectMapper objectMapper; + + @MockBean private StartGuideService service; + + private final WebTestClientHelper testClientHelper; + + @Autowired + StartGuideControllerTest(WebTestClient webTestClient) { + testClientHelper = new WebTestClientHelper(webTestClient); + } + + @SneakyThrows + @Test + void givenStartingGuideInfo_whenGettingStartGuideInfo_thenReturnsOk() { + // given + var kind = "kind1"; + var productId = "productId1"; + var guideInfoDto = + new StartGuideInfoDto( + new SellerApiServerRegistrationInfoDto(true), + new ApiMappingInfoDto(true), + new DeploymentInfoDto(true, true, false)); + when(service.getStartGuideInfo(productId, kind)).thenReturn(guideInfoDto); + // when + var path = StartGuideController.URL + "/" + productId; + testClientHelper.getAndVerify( + (uriBuilder -> uriBuilder.path(path).queryParam("kind", kind).build()), + bodyStr -> { + // then + var result = content(bodyStr, new TypeReference>() {}); + assertThat(result.getData()).isEqualTo(guideInfoDto); + }); + } + + @SneakyThrows + private T content(String response, TypeReference typeReference) { + return objectMapper.readValue(response, typeReference); + } +} diff --git a/kraken-java-sdk/kraken-java-sdk-controller/src/test/java/com/consoleconnect/kraken/operator/controller/service/start/StartGuideServiceTest.java b/kraken-java-sdk/kraken-java-sdk-controller/src/test/java/com/consoleconnect/kraken/operator/controller/service/start/StartGuideServiceTest.java new file mode 100644 index 00000000..352597fa --- /dev/null +++ b/kraken-java-sdk/kraken-java-sdk-controller/src/test/java/com/consoleconnect/kraken/operator/controller/service/start/StartGuideServiceTest.java @@ -0,0 +1,348 @@ +package com.consoleconnect.kraken.operator.controller.service.start; + +import static com.consoleconnect.kraken.operator.controller.service.start.StartGuideService.KIND_CAN_NOT_BE_EMPTY_ERROR; +import static com.consoleconnect.kraken.operator.controller.service.start.StartGuideService.PRODUCT_ID_CAN_NOT_BE_EMPTY_ERROR; +import static com.consoleconnect.kraken.operator.core.enums.AssetKindEnum.PRODUCT_BUYER; +import static com.consoleconnect.kraken.operator.core.enums.MappingStatusEnum.COMPLETE; +import static com.consoleconnect.kraken.operator.core.enums.MappingStatusEnum.INCOMPLETE; +import static com.consoleconnect.kraken.operator.core.service.UnifiedAssetService.getSearchPageRequest; +import static com.consoleconnect.kraken.operator.core.toolkit.PagingHelper.toPage; +import static java.util.List.of; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.consoleconnect.kraken.operator.controller.dto.ApiMapperDeploymentDTO; +import com.consoleconnect.kraken.operator.controller.dto.ComponentExpandDTO; +import com.consoleconnect.kraken.operator.controller.model.Environment; +import com.consoleconnect.kraken.operator.controller.service.ApiComponentService; +import com.consoleconnect.kraken.operator.controller.service.EnvironmentService; +import com.consoleconnect.kraken.operator.controller.service.ProductDeploymentService; +import com.consoleconnect.kraken.operator.core.dto.UnifiedAssetDto; +import com.consoleconnect.kraken.operator.core.entity.UnifiedAssetEntity; +import com.consoleconnect.kraken.operator.core.enums.DeployStatusEnum; +import com.consoleconnect.kraken.operator.core.enums.EnvNameEnum; +import com.consoleconnect.kraken.operator.core.enums.MappingStatusEnum; +import com.consoleconnect.kraken.operator.core.exception.KrakenException; +import com.consoleconnect.kraken.operator.core.repo.UnifiedAssetRepository; +import com.consoleconnect.kraken.operator.core.service.UnifiedAssetService; +import com.consoleconnect.kraken.operator.core.toolkit.PagingHelper; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class StartGuideServiceTest { + + public static final String STAGE_ENVIRONMENT_ID_1 = "eId1"; + public static final String PRODUCTION_ENVIRONMENT_ID_2 = "eId2"; + public static final String PRODUCT_ID = "productId1"; + public static final String KIND = "kind1"; + public static final String ASSET_PRODUCT_ID_1 = "assetProductId1"; + + private StartGuideService sut; + private ApiComponentService apiComponentServiceMock; + private UnifiedAssetRepository unifiedAssetRepositoryMock; + private UnifiedAssetService unifiedAssetServiceMock; + private EnvironmentService environmentServiceMock; + private ProductDeploymentService productDeploymentServiceMock; + + private UnifiedAssetDto product; + + @BeforeEach + void setUp() { + apiComponentServiceMock = mock(ApiComponentService.class); + unifiedAssetRepositoryMock = mock(UnifiedAssetRepository.class); + unifiedAssetServiceMock = mock(UnifiedAssetService.class); + environmentServiceMock = mock(EnvironmentService.class); + productDeploymentServiceMock = mock(ProductDeploymentService.class); + sut = + new StartGuideService( + apiComponentServiceMock, + unifiedAssetRepositoryMock, + unifiedAssetServiceMock, + environmentServiceMock, + productDeploymentServiceMock); + + product = new UnifiedAssetDto(); + product.setParentId(ASSET_PRODUCT_ID_1); + } + + @Test + void givenNoSellerApiRegistered_whenGettingAtLeastOneSellerApiRegistered_thenReturnsFalse() { + // given + givenNoBuyer(); + givenNoApiDeployedOnEnvs(); + when(unifiedAssetServiceMock.findOne(PRODUCT_ID)).thenReturn(product); + when(unifiedAssetRepositoryMock.existsByParentIdAndKind(product.getId(), KIND)) + .thenReturn(false); + // when + var result = sut.getStartGuideInfo(PRODUCT_ID, KIND); + // then + assertThat(result.getSellerApiServerRegistrationInfo().getAtLeastOneSellerApiRegistered()) + .isFalse(); + } + + @Test + void + givenAtLeastOneSellerApiRegistered_whenGettingAtLeastOneSellerApiRegistered_thenReturnsTrue() { + // given + givenNoBuyer(); + givenNoApiDeployedOnEnvs(); + when(unifiedAssetServiceMock.findOne(StartGuideServiceTest.PRODUCT_ID)).thenReturn(product); + when(unifiedAssetRepositoryMock.existsByParentIdAndKind(product.getId(), KIND)) + .thenReturn(true); + // when + var result = + sut.getStartGuideInfo(StartGuideServiceTest.PRODUCT_ID, StartGuideServiceTest.KIND); + // then + assertThat(result.getSellerApiServerRegistrationInfo().getAtLeastOneSellerApiRegistered()) + .isTrue(); + } + + @Test + void givenNoApiMappingCompleted_whenGettingAtLeastOneMappingCompleted_thenReturnsFalse() { + // given + givenNoSellerApiRegistered(product); + givenNoApiDeployedOnEnvs(); + givenNoBuyer(); + when(apiComponentServiceMock.listAllApiUseCase()).thenReturn(Collections.emptyList()); + // when + var result = + sut.getStartGuideInfo(StartGuideServiceTest.PRODUCT_ID, StartGuideServiceTest.KIND); + // then + var apiMappingInfo = result.getApiMappingInfo(); + assertThat(apiMappingInfo.getAtLeastOneMappingCompleted()).isFalse(); + } + + @Test + void + givenOnlyDefaultApiMappingCompleted_whenGettingAtLeastOneMappingCompleted_thenReturnsFalse() { + // given + givenNoSellerApiRegistered(product); + givenNoApiDeployedOnEnvs(); + givenNoBuyer(); + + var componentExpandDTO1 = new ComponentExpandDTO(); + componentExpandDTO1.setDetails( + of(targetMappingDetail(COMPLETE), targetMappingDetail(INCOMPLETE))); + var componentExpandDTO2 = new ComponentExpandDTO(); + componentExpandDTO2.setDetails(of(targetMappingDetail(COMPLETE))); + when(apiComponentServiceMock.listAllApiUseCase()) + .thenReturn(of(componentExpandDTO1, componentExpandDTO2)); + // when + var result = sut.getStartGuideInfo(StartGuideServiceTest.PRODUCT_ID, KIND); + // then + var apiMappingInfo = result.getApiMappingInfo(); + assertThat(apiMappingInfo.getAtLeastOneMappingCompleted()).isFalse(); + } + + @Test + void + givenDefaultApiMappingAndOneCompleted_whenGettingAtLeastOneMappingCompleted_thenReturnsTrue() { + // given + givenNoSellerApiRegistered(product); + givenNoApiDeployedOnEnvs(); + givenNoBuyer(); + + var componentExpandDTO1 = new ComponentExpandDTO(); + componentExpandDTO1.setDetails( + of( + targetMappingDetail(COMPLETE), + targetMappingDetail(COMPLETE), + targetMappingDetail(INCOMPLETE))); + var componentExpandDTO2 = new ComponentExpandDTO(); + componentExpandDTO2.setDetails(of(targetMappingDetail(COMPLETE))); + when(apiComponentServiceMock.listAllApiUseCase()) + .thenReturn(of(componentExpandDTO1, componentExpandDTO2)); + // when + var result = + sut.getStartGuideInfo(StartGuideServiceTest.PRODUCT_ID, StartGuideServiceTest.KIND); + // then + var apiMappingInfo = result.getApiMappingInfo(); + assertThat(apiMappingInfo.getAtLeastOneMappingCompleted()).isTrue(); + } + + @Test + void givenNoBuyerRegistered_whenGettingAtLestOnBuyerRegistered_thenReturnsFalse() { + // given + var assetEntity = new UnifiedAssetEntity(); + var parentId = UUID.randomUUID(); + assetEntity.setId(parentId); + givenNoSellerApiRegistered(product); + givenNoApiDeployedOnEnvs(); + + when(unifiedAssetServiceMock.findOneByIdOrKey(StartGuideServiceTest.PRODUCT_ID)) + .thenReturn(assetEntity); + when(unifiedAssetRepositoryMock.existsByParentIdAndKind( + parentId.toString(), PRODUCT_BUYER.getKind())) + .thenReturn(false); + // when + var result = + sut.getStartGuideInfo(StartGuideServiceTest.PRODUCT_ID, StartGuideServiceTest.KIND); + // then + assertThat(result.getDeploymentInfo().getAtLeastOneBuyerRegistered()).isFalse(); + } + + @Test + void givenAtLeastOneBuyerRegistered_whenGettingAtLestOnBuyerRegistered_thenReturnsTrue() { + // given + var parentId = UUID.randomUUID(); + var assetEntity = new UnifiedAssetEntity(); + assetEntity.setId(parentId); + givenNoSellerApiRegistered(product); + givenNoApiDeployedOnEnvs(); + when(unifiedAssetServiceMock.findOneByIdOrKey(StartGuideServiceTest.PRODUCT_ID)) + .thenReturn(assetEntity); + when(unifiedAssetRepositoryMock.existsByParentIdAndKind( + parentId.toString(), PRODUCT_BUYER.getKind())) + .thenReturn(true); + // when + var result = + sut.getStartGuideInfo(StartGuideServiceTest.PRODUCT_ID, StartGuideServiceTest.KIND); + // then + assertThat(result.getDeploymentInfo().getAtLeastOneBuyerRegistered()).isTrue(); + } + + @Test + void givenNoApiDeployedToEnvs_whenGettingAtLeastOneApiDeployedToEnv_thenReturnsFalse() { + // given + givenNoSellerApiRegistered(product); + givenNoBuyer(); + + givenNoApiDeployedOnEnvs(); + + // when + var result = + sut.getStartGuideInfo(StartGuideServiceTest.PRODUCT_ID, StartGuideServiceTest.KIND); + // then + assertThat(result.getDeploymentInfo().getAtLeastOneApiDeployedToStage()).isFalse(); + assertThat(result.getDeploymentInfo().getAtLeastOneApiDeployedToProduction()).isFalse(); + } + + @Test + void givenAtLeastOneApiDeployedToEnvs_whenGettingAtLeastOneApiDeployedToEnv_thenReturnsTrue() { + // given + givenNoSellerApiRegistered(product); + givenNoBuyer(); + + when(environmentServiceMock.search( + StartGuideServiceTest.PRODUCT_ID, getSearchPageRequest(0, EnvNameEnum.values().length))) + .thenReturn(toPage(environments(), 0, 2)); + when(productDeploymentServiceMock.retrieveApiMapperDeployments( + STAGE_ENVIRONMENT_ID_1, null, DeployStatusEnum.SUCCESS, getSearchPageRequest(0, 1))) + .thenReturn( + PagingHelper.toPage( + of(firstDefaultApiMapper(), secondDefaultApiMapper(), new ApiMapperDeploymentDTO()), + 0, + 3)); + when(productDeploymentServiceMock.retrieveApiMapperDeployments( + PRODUCTION_ENVIRONMENT_ID_2, + null, + DeployStatusEnum.SUCCESS, + getSearchPageRequest(0, 1))) + .thenReturn( + PagingHelper.toPage( + of(firstDefaultApiMapper(), secondDefaultApiMapper(), new ApiMapperDeploymentDTO()), + 0, + 3)); + // when + var result = + sut.getStartGuideInfo(StartGuideServiceTest.PRODUCT_ID, StartGuideServiceTest.KIND); + // then + var deploymentInfo = result.getDeploymentInfo(); + assertThat(deploymentInfo.getAtLeastOneApiDeployedToStage()).isTrue(); + assertThat(deploymentInfo.getAtLeastOneApiDeployedToProduction()).isTrue(); + } + + @Test + void givenNoProductId_whenGetStartGuideInfo_thenReturnsError() { + // given + // when + var ex = + assertThrows( + KrakenException.class, + () -> { + sut.getStartGuideInfo("", KIND); + }); + + // then + assertThat(ex.getCode()).isEqualTo(400); + assertThat(ex.getMessage()).isEqualTo(PRODUCT_ID_CAN_NOT_BE_EMPTY_ERROR); + } + + @Test + void givenNoKind_whenGetStartGuideInfo_thenReturnsError() { + // given + // when + var ex = + assertThrows( + KrakenException.class, + () -> { + sut.getStartGuideInfo(PRODUCT_ID, ""); + }); + + // then + assertThat(ex.getCode()).isEqualTo(400); + assertThat(ex.getMessage()).isEqualTo(KIND_CAN_NOT_BE_EMPTY_ERROR); + } + + private ApiMapperDeploymentDTO secondDefaultApiMapper() { + return new ApiMapperDeploymentDTO(); + } + + private ApiMapperDeploymentDTO firstDefaultApiMapper() { + return new ApiMapperDeploymentDTO(); + } + + private List environments() { + Environment e1 = new Environment(); + e1.setId(STAGE_ENVIRONMENT_ID_1); + e1.setName(EnvNameEnum.STAGE.name()); + Environment e2 = new Environment(); + e2.setName(EnvNameEnum.PRODUCTION.name()); + e2.setId(PRODUCTION_ENVIRONMENT_ID_2); + return of(e1, e2); + } + + private ComponentExpandDTO.TargetMappingDetail targetMappingDetail( + MappingStatusEnum mappingStatus) { + var targetMappingDetail = new ComponentExpandDTO.TargetMappingDetail(); + targetMappingDetail.setMappingStatus(mappingStatus.getDesc()); + return targetMappingDetail; + } + + private void givenNoSellerApiRegistered(UnifiedAssetDto product) { + when(unifiedAssetServiceMock.findOne(StartGuideServiceTest.PRODUCT_ID)).thenReturn(product); + when(unifiedAssetRepositoryMock.existsByParentIdAndKind( + product.getId(), StartGuideServiceTest.KIND)) + .thenReturn(false); + } + + private void givenNoApiDeployedOnEnvs() { + when(environmentServiceMock.search( + StartGuideServiceTest.PRODUCT_ID, getSearchPageRequest(0, EnvNameEnum.values().length))) + .thenReturn(toPage(environments(), 0, 2)); + when(productDeploymentServiceMock.retrieveApiMapperDeployments( + STAGE_ENVIRONMENT_ID_1, null, DeployStatusEnum.SUCCESS, getSearchPageRequest(0, 1))) + .thenReturn(PagingHelper.toPage(Collections.emptyList(), 0, 0)); + when(productDeploymentServiceMock.retrieveApiMapperDeployments( + PRODUCTION_ENVIRONMENT_ID_2, + null, + DeployStatusEnum.SUCCESS, + getSearchPageRequest(0, 1))) + .thenReturn(PagingHelper.toPage(Collections.emptyList(), 0, 0)); + } + + private void givenNoBuyer() { + UnifiedAssetEntity assetEntity = new UnifiedAssetEntity(); + assetEntity.setId(UUID.randomUUID()); + when(unifiedAssetServiceMock.findOneByIdOrKey(StartGuideServiceTest.PRODUCT_ID)) + .thenReturn(assetEntity); + when(unifiedAssetRepositoryMock.existsByParentIdAndKind( + assetEntity.getId().toString(), PRODUCT_BUYER.getKind())) + .thenReturn(false); + } +} diff --git a/kraken-java-sdk/kraken-java-sdk-core/src/main/java/com/consoleconnect/kraken/operator/core/repo/UnifiedAssetRepository.java b/kraken-java-sdk/kraken-java-sdk-core/src/main/java/com/consoleconnect/kraken/operator/core/repo/UnifiedAssetRepository.java index a854d9ad..90da3e5c 100644 --- a/kraken-java-sdk/kraken-java-sdk-core/src/main/java/com/consoleconnect/kraken/operator/core/repo/UnifiedAssetRepository.java +++ b/kraken-java-sdk/kraken-java-sdk-core/src/main/java/com/consoleconnect/kraken/operator/core/repo/UnifiedAssetRepository.java @@ -93,4 +93,6 @@ Page findBuyers( Pageable pageable); boolean existsByKey(String key); + + boolean existsByParentIdAndKind(String parentId, String kind); } From 6d96c28824c34dd83522b6272a95f5d81b025d77 Mon Sep 17 00:00:00 2001 From: jaroslawmalekcodete Date: Tue, 5 Nov 2024 11:58:51 +0100 Subject: [PATCH 2/5] feat(sdk): kraken-19 - add endpoints for api request statistics (#70) --- .../EnvAPIActivityStatisticsController.java | 95 +++ .../ApiRequestActivityStatistics.java | 13 + .../dto/statistics/EndpointUsage.java | 13 + .../statistics/ErrorApiRequestStatistics.java | 13 + .../dto/statistics/ErrorBreakdown.java | 15 + .../MostPopularEndpointStatistics.java | 13 + .../dto/statistics/RequestStatistics.java | 13 + .../ApiActivityStatisticsService.java | 197 +++++++ ...nvAPIActivityStatisticsControllerTest.java | 160 +++++ .../ApiActivityStatisticsServiceTest.java | 550 ++++++++++++++++++ .../core/repo/ApiActivityLogRepository.java | 19 + .../request/ApiStatisticsSearchRequest.java | 14 + 12 files changed, 1115 insertions(+) create mode 100644 kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/api/EnvAPIActivityStatisticsController.java create mode 100644 kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/statistics/ApiRequestActivityStatistics.java create mode 100644 kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/statistics/EndpointUsage.java create mode 100644 kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/statistics/ErrorApiRequestStatistics.java create mode 100644 kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/statistics/ErrorBreakdown.java create mode 100644 kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/statistics/MostPopularEndpointStatistics.java create mode 100644 kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/statistics/RequestStatistics.java create mode 100644 kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/service/statistics/ApiActivityStatisticsService.java create mode 100644 kraken-java-sdk/kraken-java-sdk-controller/src/test/java/com/consoleconnect/kraken/operator/controller/api/EnvAPIActivityStatisticsControllerTest.java create mode 100644 kraken-java-sdk/kraken-java-sdk-controller/src/test/java/com/consoleconnect/kraken/operator/controller/service/statistics/ApiActivityStatisticsServiceTest.java create mode 100644 kraken-java-sdk/kraken-java-sdk-core/src/main/java/com/consoleconnect/kraken/operator/core/request/ApiStatisticsSearchRequest.java diff --git a/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/api/EnvAPIActivityStatisticsController.java b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/api/EnvAPIActivityStatisticsController.java new file mode 100644 index 00000000..342ffc6b --- /dev/null +++ b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/api/EnvAPIActivityStatisticsController.java @@ -0,0 +1,95 @@ +package com.consoleconnect.kraken.operator.controller.api; + +import static com.consoleconnect.kraken.operator.core.model.HttpResponse.ok; + +import com.consoleconnect.kraken.operator.controller.dto.statistics.ApiRequestActivityStatistics; +import com.consoleconnect.kraken.operator.controller.dto.statistics.ErrorApiRequestStatistics; +import com.consoleconnect.kraken.operator.controller.dto.statistics.MostPopularEndpointStatistics; +import com.consoleconnect.kraken.operator.controller.service.statistics.ApiActivityStatisticsService; +import com.consoleconnect.kraken.operator.core.model.HttpResponse; +import com.consoleconnect.kraken.operator.core.request.ApiStatisticsSearchRequest; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.time.ZonedDateTime; +import lombok.AllArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@AllArgsConstructor +@RestController() +@RequestMapping( + value = "/products/{productId}/envs/{envId}/statistics", + produces = MediaType.APPLICATION_JSON_VALUE) +@Tag(name = "API Activities Statistics", description = "API Activities Statistics") +public class EnvAPIActivityStatisticsController { + + private final ApiActivityStatisticsService apiActivityStatisticsService; + + @Operation(summary = "Load api activity request statistics") + @GetMapping("/api-activity-requests") + public HttpResponse getRequestStatistics( + @PathVariable("productId") String productId, + @PathVariable("envId") String envId, + @RequestParam(value = "requestStartTime") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + ZonedDateTime requestStartTime, + @RequestParam(value = "requestEndTime") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + ZonedDateTime requestEndTime, + @RequestParam(value = "buyerId", required = false) String buyerId) { + + return ok( + apiActivityStatisticsService.loadRequestStatistics( + ApiStatisticsSearchRequest.builder() + .env(envId) + .buyerId(buyerId) + .queryStart(requestStartTime) + .queryEnd(requestEndTime) + .build())); + } + + @Operation(summary = "Load error request statistics") + @GetMapping("/error-requests") + public HttpResponse getErrorStatistics( + @PathVariable("productId") String productId, + @PathVariable("envId") String envId, + @RequestParam(value = "requestStartTime") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + ZonedDateTime requestStartTime, + @RequestParam(value = "requestEndTime") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + ZonedDateTime requestEndTime, + @RequestParam(value = "buyerId", required = false) String buyerId) { + + return ok( + apiActivityStatisticsService.loadErrorsStatistics( + ApiStatisticsSearchRequest.builder() + .env(envId) + .buyerId(buyerId) + .queryStart(requestStartTime) + .queryEnd(requestEndTime) + .build())); + } + + @Operation(summary = "Load most popular endpoint statistics") + @GetMapping("/most-popular-endpoint") + public HttpResponse getMostPopularEndpointStatistics( + @PathVariable("productId") String productId, + @PathVariable("envId") String envId, + @RequestParam(value = "requestStartTime") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + ZonedDateTime requestStartTime, + @RequestParam(value = "requestEndTime") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + ZonedDateTime requestEndTime, + @RequestParam(value = "buyerId", required = false) String buyerId) { + + return ok( + apiActivityStatisticsService.loadMostPopularEndpointStatistics( + ApiStatisticsSearchRequest.builder() + .env(envId) + .buyerId(buyerId) + .queryStart(requestStartTime) + .queryEnd(requestEndTime) + .build())); + } +} diff --git a/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/statistics/ApiRequestActivityStatistics.java b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/statistics/ApiRequestActivityStatistics.java new file mode 100644 index 00000000..88e67f0b --- /dev/null +++ b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/statistics/ApiRequestActivityStatistics.java @@ -0,0 +1,13 @@ +package com.consoleconnect.kraken.operator.controller.dto.statistics; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ApiRequestActivityStatistics { + private List requestStatistics; +} diff --git a/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/statistics/EndpointUsage.java b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/statistics/EndpointUsage.java new file mode 100644 index 00000000..8013c37f --- /dev/null +++ b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/statistics/EndpointUsage.java @@ -0,0 +1,13 @@ +package com.consoleconnect.kraken.operator.controller.dto.statistics; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class EndpointUsage { + private String method; + private String endpoint; + private Long usage; + private double popularity; +} diff --git a/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/statistics/ErrorApiRequestStatistics.java b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/statistics/ErrorApiRequestStatistics.java new file mode 100644 index 00000000..41c93f17 --- /dev/null +++ b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/statistics/ErrorApiRequestStatistics.java @@ -0,0 +1,13 @@ +package com.consoleconnect.kraken.operator.controller.dto.statistics; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ErrorApiRequestStatistics { + private List errorBreakdowns; +} diff --git a/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/statistics/ErrorBreakdown.java b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/statistics/ErrorBreakdown.java new file mode 100644 index 00000000..07807562 --- /dev/null +++ b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/statistics/ErrorBreakdown.java @@ -0,0 +1,15 @@ +package com.consoleconnect.kraken.operator.controller.dto.statistics; + +import java.time.LocalDate; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ErrorBreakdown { + private LocalDate date; + private Map errors; +} diff --git a/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/statistics/MostPopularEndpointStatistics.java b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/statistics/MostPopularEndpointStatistics.java new file mode 100644 index 00000000..6a4f985b --- /dev/null +++ b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/statistics/MostPopularEndpointStatistics.java @@ -0,0 +1,13 @@ +package com.consoleconnect.kraken.operator.controller.dto.statistics; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class MostPopularEndpointStatistics { + private List endpointUsages; +} diff --git a/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/statistics/RequestStatistics.java b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/statistics/RequestStatistics.java new file mode 100644 index 00000000..f7d16289 --- /dev/null +++ b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/statistics/RequestStatistics.java @@ -0,0 +1,13 @@ +package com.consoleconnect.kraken.operator.controller.dto.statistics; + +import java.time.LocalDate; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class RequestStatistics { + private LocalDate date; + private Long success; + private Long error; +} diff --git a/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/service/statistics/ApiActivityStatisticsService.java b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/service/statistics/ApiActivityStatisticsService.java new file mode 100644 index 00000000..9acefadd --- /dev/null +++ b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/service/statistics/ApiActivityStatisticsService.java @@ -0,0 +1,197 @@ +package com.consoleconnect.kraken.operator.controller.service.statistics; + +import com.consoleconnect.kraken.operator.controller.dto.statistics.ApiRequestActivityStatistics; +import com.consoleconnect.kraken.operator.controller.dto.statistics.EndpointUsage; +import com.consoleconnect.kraken.operator.controller.dto.statistics.ErrorApiRequestStatistics; +import com.consoleconnect.kraken.operator.controller.dto.statistics.ErrorBreakdown; +import com.consoleconnect.kraken.operator.controller.dto.statistics.MostPopularEndpointStatistics; +import com.consoleconnect.kraken.operator.controller.dto.statistics.RequestStatistics; +import com.consoleconnect.kraken.operator.core.entity.AbstractHttpEntity; +import com.consoleconnect.kraken.operator.core.entity.ApiActivityLogEntity; +import com.consoleconnect.kraken.operator.core.repo.ApiActivityLogRepository; +import com.consoleconnect.kraken.operator.core.request.ApiStatisticsSearchRequest; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +public class ApiActivityStatisticsService { + + public static final String CREATED_AT = "createdAt"; + public static final String ENV = "env"; + public static final String CALL_SEQ = "callSeq"; + public static final String HTTP_STATUS_CODE = "httpStatusCode"; + public static final String CALL_SEQ_ZERO = "0"; + public static final String BUYER = "buyer"; + public static final int NUMBER_OF_MOST_POPULAR_ENDPOINT_LIMIT = 7; + + private final ApiActivityLogRepository repository; + + public ApiRequestActivityStatistics loadRequestStatistics( + ApiStatisticsSearchRequest searchRequest) { + var zoneId = searchRequest.getQueryStart().getZone(); + var logs = getApiActivityLogEntities(searchRequest); + var logsGroupedByDay = groupByDayAndSuccessError(zoneId, logs); + return createApiRequestActivityStatistics(logsGroupedByDay); + } + + private List getApiActivityLogEntities( + ApiStatisticsSearchRequest searchRequest) { + Specification spec = + (root, query, criteriaBuilder) -> { + var predicateList = predicates(searchRequest, root, criteriaBuilder); + if (searchRequest.getBuyerId() != null) { + predicateList.add(criteriaBuilder.equal(root.get(BUYER), searchRequest.getBuyerId())); + } + return query.where(predicateList.toArray(new Predicate[0])).getRestriction(); + }; + return repository.findAll(spec); + } + + private Map> groupByDayAndSuccessError( + ZoneId zoneId, List logs) { + return logs.stream() + .collect( + Collectors.groupingBy( + entity -> + entity.getCreatedAt().withZoneSameInstant(zoneId).truncatedTo(ChronoUnit.DAYS), + Collectors.groupingBy( + entity -> status(entity.getHttpStatusCode()), Collectors.counting()))); + } + + private ApiRequestActivityStatistics createApiRequestActivityStatistics( + Map> logsGroupedByDayAndHttpStatus) { + var stats = + logsGroupedByDayAndHttpStatus.entrySet().stream() + .map( + dateEntry -> + new RequestStatistics( + dateEntry.getKey().toLocalDate(), + dateEntry.getValue().get(RequestStatus.SUCCESS), + dateEntry.getValue().get(RequestStatus.ERROR))) + .sorted(Comparator.comparing(RequestStatistics::getDate)) + .toList(); + return new ApiRequestActivityStatistics(stats); + } + + private RequestStatus status(Integer statusCode) { + return (HttpStatus.valueOf(statusCode).is2xxSuccessful()) + ? RequestStatus.SUCCESS + : RequestStatus.ERROR; + } + + public ErrorApiRequestStatistics loadErrorsStatistics(ApiStatisticsSearchRequest searchRequest) { + var zoneId = searchRequest.getQueryStart().getZone(); + var errorLogs = getApiActivityLogErrorEntities(searchRequest); + var logsGroupedByDayAndErrors = groupByDayAndStatus(zoneId, errorLogs); + return createErrorApiRequestStatistics(logsGroupedByDayAndErrors); + } + + private List getApiActivityLogErrorEntities( + ApiStatisticsSearchRequest searchRequest) { + Specification spec = + (root, query, criteriaBuilder) -> { + var predicateList = predicates(searchRequest, root, criteriaBuilder); + if (searchRequest.getBuyerId() != null) { + predicateList.add(criteriaBuilder.equal(root.get(BUYER), searchRequest.getBuyerId())); + } + predicateList.add(criteriaBuilder.greaterThanOrEqualTo(root.get(HTTP_STATUS_CODE), 400)); + predicateList.add(criteriaBuilder.lessThan(root.get(HTTP_STATUS_CODE), 600)); + return query.where(predicateList.toArray(new Predicate[0])).getRestriction(); + }; + return repository.findAll(spec); + } + + private Map> groupByDayAndStatus( + ZoneId zoneId, List logs) { + return logs.stream() + .collect( + Collectors.groupingBy( + entity -> + entity.getCreatedAt().withZoneSameInstant(zoneId).truncatedTo(ChronoUnit.DAYS), + Collectors.groupingBy( + AbstractHttpEntity::getHttpStatusCode, Collectors.counting()))); + } + + private ErrorApiRequestStatistics createErrorApiRequestStatistics( + Map> stats) { + var data = + stats.entrySet().stream() + .map( + dateEntry -> + new ErrorBreakdown(dateEntry.getKey().toLocalDate(), dateEntry.getValue())) + .sorted(Comparator.comparing(ErrorBreakdown::getDate)) + .toList(); + return new ErrorApiRequestStatistics(data); + } + + public MostPopularEndpointStatistics loadMostPopularEndpointStatistics( + ApiStatisticsSearchRequest searchRequest) { + List endpointPerUsage = + repository.findTopEndpoints( + searchRequest.getEnv(), + searchRequest.getQueryStart(), + searchRequest.getQueryEnd(), + CALL_SEQ_ZERO, + searchRequest.getBuyerId(), + NUMBER_OF_MOST_POPULAR_ENDPOINT_LIMIT); + long numberOfAllRequests = + repository.count( + (root, query, criteriaBuilder) -> { + var predicateList = predicates(searchRequest, root, criteriaBuilder); + return query.where(predicateList.toArray(new Predicate[0])).getRestriction(); + }); + + var data = + endpointPerUsage.stream() + .map(endpoint -> createEndpointUsage(endpoint, numberOfAllRequests)) + .toList(); + return new MostPopularEndpointStatistics(data); + } + + private EndpointUsage createEndpointUsage(Object[] endpoint, long numberOfAllRequests) { + var path = (String) endpoint[0]; + var method = (String) endpoint[1]; + var number = (Long) endpoint[2]; + var percentage = + new BigDecimal(number) + .divide(new BigDecimal(numberOfAllRequests), 5, RoundingMode.DOWN) + .multiply(new BigDecimal(100)) + .setScale(2, RoundingMode.DOWN); + return new EndpointUsage(method, path, number, percentage.doubleValue()); + } + + private List predicates( + ApiStatisticsSearchRequest searchRequest, + Root root, + CriteriaBuilder criteriaBuilder) { + var predicateList = new ArrayList(); + predicateList.add(criteriaBuilder.equal(root.get(ENV), searchRequest.getEnv())); + predicateList.add(criteriaBuilder.equal(root.get(CALL_SEQ), CALL_SEQ_ZERO)); + predicateList.add( + criteriaBuilder.greaterThanOrEqualTo(root.get(CREATED_AT), searchRequest.getQueryStart())); + predicateList.add( + criteriaBuilder.lessThanOrEqualTo(root.get(CREATED_AT), searchRequest.getQueryEnd())); + return predicateList; + } + + enum RequestStatus { + SUCCESS, + ERROR + } +} diff --git a/kraken-java-sdk/kraken-java-sdk-controller/src/test/java/com/consoleconnect/kraken/operator/controller/api/EnvAPIActivityStatisticsControllerTest.java b/kraken-java-sdk/kraken-java-sdk-controller/src/test/java/com/consoleconnect/kraken/operator/controller/api/EnvAPIActivityStatisticsControllerTest.java new file mode 100644 index 00000000..21f10662 --- /dev/null +++ b/kraken-java-sdk/kraken-java-sdk-controller/src/test/java/com/consoleconnect/kraken/operator/controller/api/EnvAPIActivityStatisticsControllerTest.java @@ -0,0 +1,160 @@ +package com.consoleconnect.kraken.operator.controller.api; + +import static java.util.List.of; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import com.consoleconnect.kraken.operator.config.TestApplication; +import com.consoleconnect.kraken.operator.controller.WebTestClientHelper; +import com.consoleconnect.kraken.operator.controller.dto.statistics.ApiRequestActivityStatistics; +import com.consoleconnect.kraken.operator.controller.dto.statistics.EndpointUsage; +import com.consoleconnect.kraken.operator.controller.dto.statistics.ErrorApiRequestStatistics; +import com.consoleconnect.kraken.operator.controller.dto.statistics.ErrorBreakdown; +import com.consoleconnect.kraken.operator.controller.dto.statistics.MostPopularEndpointStatistics; +import com.consoleconnect.kraken.operator.controller.dto.statistics.RequestStatistics; +import com.consoleconnect.kraken.operator.controller.service.statistics.ApiActivityStatisticsService; +import com.consoleconnect.kraken.operator.core.model.HttpResponse; +import com.consoleconnect.kraken.operator.core.request.ApiStatisticsSearchRequest; +import com.consoleconnect.kraken.operator.test.AbstractIntegrationTest; +import com.consoleconnect.kraken.operator.test.MockIntegrationTest; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.UUID; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.web.reactive.server.WebTestClient; + +@ActiveProfiles("test-auth-server-enabled") +@MockIntegrationTest +@ContextConfiguration(classes = TestApplication.class) +class EnvAPIActivityStatisticsControllerTest extends AbstractIntegrationTest { + + public static final ZonedDateTime START_DATE = ZonedDateTime.parse("2023-10-24T00:00:00-03:00"); + public static final ZonedDateTime END_DATE = ZonedDateTime.parse("2023-10-25T00:00:00-03:00"); + public static final String BUYER_ID_1 = "buyerId1"; + @MockBean private ApiActivityStatisticsService service; + @Autowired private ObjectMapper objectMapper; + + private final WebTestClientHelper testClientHelper; + + @Autowired + EnvAPIActivityStatisticsControllerTest(WebTestClient webTestClient) { + testClientHelper = new WebTestClientHelper(webTestClient); + } + + @Test + void givenApiActivityLogs_whenGettingApiRequestStatistics_thenReturnsOk() { + // given + var envId = UUID.randomUUID(); + var searchRequest = + ApiStatisticsSearchRequest.builder() + .env(envId.toString()) + .queryStart(START_DATE) + .queryEnd(END_DATE) + .buyerId(BUYER_ID_1) + .build(); + + var apiRequestActivityStatistics = + new ApiRequestActivityStatistics( + of(new RequestStatistics(START_DATE.toLocalDate(), 100L, 200L))); + when(service.loadRequestStatistics(searchRequest)).thenReturn(apiRequestActivityStatistics); + // when + var path = + String.format("/products/%s/envs/%s/statistics/api-activity-requests", "productId", envId); + testClientHelper.getAndVerify( + (uriBuilder -> + uriBuilder + .path(path) + .queryParam("requestStartTime", START_DATE) + .queryParam("requestEndTime", END_DATE) + .queryParam("buyerId", BUYER_ID_1) + .build()), + bodyStr -> { + // then + var result = + content(bodyStr, new TypeReference>() {}); + assertThat(result.getData()).isEqualTo(apiRequestActivityStatistics); + }); + } + + @Test + void givenApiActivityLogs_whenLoadErrorsStatistics_thenReturnsOk() { + // given + var envId = UUID.randomUUID(); + + var searchRequest = + ApiStatisticsSearchRequest.builder() + .env(envId.toString()) + .queryStart(START_DATE) + .queryEnd(END_DATE) + .buyerId(BUYER_ID_1) + .build(); + + var errors = + new ErrorApiRequestStatistics( + of(new ErrorBreakdown(START_DATE.toLocalDate(), new HashMap<>()))); + when(service.loadErrorsStatistics(searchRequest)).thenReturn(errors); + // when + var path = String.format("/products/%s/envs/%s/statistics/error-requests", "productId", envId); + testClientHelper.getAndVerify( + (uriBuilder -> + uriBuilder + .path(path) + .queryParam("requestStartTime", START_DATE) + .queryParam("requestEndTime", END_DATE) + .queryParam("buyerId", BUYER_ID_1) + .build()), + bodyStr -> { + // then + var result = + content(bodyStr, new TypeReference>() {}); + assertThat(result.getData()).isEqualTo(errors); + }); + } + + @Test + void givenApiActivityLogs_whenLoadMostPopularEndpointStatistics_thenReturnsOk() { + // given + var envId = UUID.randomUUID(); + + var searchRequest = + ApiStatisticsSearchRequest.builder() + .env(envId.toString()) + .queryStart(START_DATE) + .queryEnd(END_DATE) + .buyerId(BUYER_ID_1) + .build(); + + var popular = + new MostPopularEndpointStatistics(of(new EndpointUsage("GEt", "/path/1", 100L, 1.1f))); + when(service.loadMostPopularEndpointStatistics(searchRequest)).thenReturn(popular); + // when + var path = + String.format("/products/%s/envs/%s/statistics/most-popular-endpoint", "productId", envId); + testClientHelper.getAndVerify( + (uriBuilder -> + uriBuilder + .path(path) + .queryParam("requestStartTime", START_DATE) + .queryParam("requestEndTime", END_DATE) + .queryParam("buyerId", BUYER_ID_1) + .build()), + bodyStr -> { + // then + var result = + content(bodyStr, new TypeReference>() {}); + assertThat(result.getData()).isEqualTo(popular); + }); + } + + @SneakyThrows + private T content(String response, TypeReference typeReference) { + return objectMapper.readValue(response, typeReference); + } +} diff --git a/kraken-java-sdk/kraken-java-sdk-controller/src/test/java/com/consoleconnect/kraken/operator/controller/service/statistics/ApiActivityStatisticsServiceTest.java b/kraken-java-sdk/kraken-java-sdk-controller/src/test/java/com/consoleconnect/kraken/operator/controller/service/statistics/ApiActivityStatisticsServiceTest.java new file mode 100644 index 00000000..1f978f8a --- /dev/null +++ b/kraken-java-sdk/kraken-java-sdk-controller/src/test/java/com/consoleconnect/kraken/operator/controller/service/statistics/ApiActivityStatisticsServiceTest.java @@ -0,0 +1,550 @@ +package com.consoleconnect.kraken.operator.controller.service.statistics; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.consoleconnect.kraken.operator.config.TestApplication; +import com.consoleconnect.kraken.operator.controller.handler.ClientAPIAuditLogEventHandler; +import com.consoleconnect.kraken.operator.core.client.ClientEvent; +import com.consoleconnect.kraken.operator.core.client.ClientEventTypeEnum; +import com.consoleconnect.kraken.operator.core.dto.ApiActivityLog; +import com.consoleconnect.kraken.operator.core.request.ApiStatisticsSearchRequest; +import com.consoleconnect.kraken.operator.core.toolkit.JsonToolkit; +import com.consoleconnect.kraken.operator.test.AbstractIntegrationTest; +import com.consoleconnect.kraken.operator.test.MockIntegrationTest; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.stream.IntStream; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; + +@ActiveProfiles("test-auth-server-enabled") +@MockIntegrationTest +@ContextConfiguration(classes = TestApplication.class) +class ApiActivityStatisticsServiceTest extends AbstractIntegrationTest { + + public static final String BUYER_ID_1 = "buyerId1"; + public static final String BUYER_ID_2 = "buyerId2"; + public static final String NOW_WITH_TIMEZONE = "2023-10-24T05:00:00+02:00"; + + @Autowired private ApiActivityStatisticsService sut; + @Autowired private ClientAPIAuditLogEventHandler clientAPIAuditLogEventHandler; + + @Test + void givenNoLogsForEnv_whenLoadRequestStatistics_thenEmptyResult() { + // given + var envId = UUID.randomUUID(); + var now = ZonedDateTime.parse(NOW_WITH_TIMEZONE); + var searchRequest = + ApiStatisticsSearchRequest.builder() + .env(envId.toString()) + .queryStart(now.minusDays(2)) + .queryEnd(now) + .build(); + // when + var result = sut.loadRequestStatistics(searchRequest); + // then + assertThat(result.getRequestStatistics()).isEmpty(); + } + + @Test + void givenLogsForEnv_whenLoadRequestStatisticsOutOfTheTimeRage_thenEmptyResult() { + // given + var envId = UUID.randomUUID(); + var now = ZonedDateTime.parse(NOW_WITH_TIMEZONE); + addApiLogActivity(envId.toString(), createPayloads(toUTC(now))); + var searchRequest = + ApiStatisticsSearchRequest.builder() + .env(envId.toString()) + .queryStart(now.minusDays(4)) + .queryEnd(now.minusDays(2)) + .build(); + // when + var result = sut.loadRequestStatistics(searchRequest); + // then + assertThat(result.getRequestStatistics()).isEmpty(); + } + + private ZonedDateTime toUTC(ZonedDateTime now) { + return now.withZoneSameInstant(ZoneId.of("UTC")); + } + + @Test + void givenApiActivityLogs_whenLoadRequestStatistics_thenReturnsApiRequestsStatisticsSortedAsc() { + // given + var envId = UUID.randomUUID(); + var now = ZonedDateTime.parse(NOW_WITH_TIMEZONE); + addApiLogActivity(envId.toString(), createPayloads(toUTC(now))); + var searchRequest = + ApiStatisticsSearchRequest.builder() + .env(envId.toString()) + .queryStart(now.minusDays(2)) + .queryEnd(now) + .build(); + // when + var result = sut.loadRequestStatistics(searchRequest); + // then + assertThat(result.getRequestStatistics()).hasSize(2); + var statsFor1Day = result.getRequestStatistics().get(0); + var statsFor2Day = result.getRequestStatistics().get(1); + assertThat(statsFor1Day.getDate()).isBefore(statsFor2Day.getDate()); + assertThat(statsFor1Day.getSuccess()).isEqualTo(8); + assertThat(statsFor1Day.getError()).isEqualTo(8); + assertThat(statsFor2Day.getSuccess()).isEqualTo(12); + assertThat(statsFor2Day.getError()).isEqualTo(12); + } + + @Test + void givenApiActivityLogs_whenLoadRequestStatisticsByBuyerId_thenReturnsApiRequestsStatistics() { + // given + var envId = UUID.randomUUID(); + var now = ZonedDateTime.parse(NOW_WITH_TIMEZONE); + addApiLogActivity(envId.toString(), createPayloads(toUTC(now))); + var searchRequest = + ApiStatisticsSearchRequest.builder() + .env(envId.toString()) + .queryStart(now.minusDays(2)) + .queryEnd(now) + .buyerId(BUYER_ID_1) + .build(); + // when + var result = sut.loadRequestStatistics(searchRequest); + // then + assertThat(result.getRequestStatistics()).hasSize(2); + var statsFor1Day = result.getRequestStatistics().get(0); + assertThat(statsFor1Day.getSuccess()).isEqualTo(8); + assertThat(statsFor1Day.getError()).isEqualTo(4); + var statsFor2Day = result.getRequestStatistics().get(1); + assertThat(statsFor2Day.getSuccess()).isEqualTo(12); + assertThat(statsFor2Day.getError()).isEqualTo(6); + } + + @Test + void givenNoErrorLogsForEnv_whenLoadErrorsStatistics_thenEmptyResult() { + // given + var envId = UUID.randomUUID(); + var now = ZonedDateTime.parse(NOW_WITH_TIMEZONE); + var searchRequest = + ApiStatisticsSearchRequest.builder() + .env(envId.toString()) + .queryStart(now.minusDays(2)) + .queryEnd(now) + .build(); + // when + var result = sut.loadErrorsStatistics(searchRequest); + // then + assertThat(result.getErrorBreakdowns()).isEmpty(); + } + + @Test + void givenErrorLogsForEnv_whenLoadErrorsStatisticsOutOfTheTimeRage_thenEmptyResult() { + // given + var envId = UUID.randomUUID(); + var now = ZonedDateTime.parse(NOW_WITH_TIMEZONE); + addApiLogActivity(envId.toString(), createPayloads(toUTC(now))); + var searchRequest = + ApiStatisticsSearchRequest.builder() + .env(envId.toString()) + .queryStart(now.minusDays(4)) + .queryEnd(now.minusDays(2)) + .build(); + // when + var result = sut.loadErrorsStatistics(searchRequest); + // then + assertThat(result.getErrorBreakdowns()).isEmpty(); + } + + @Test + void givenErrorApiActivityLogs_whenLoadErrorsStatistics_thenReturnsErrorStatisticsSortedAsc() { + // given + var envId = UUID.randomUUID(); + var now = ZonedDateTime.parse(NOW_WITH_TIMEZONE); + addApiLogActivity(envId.toString(), createPayloads(toUTC(now))); + var searchRequest = + ApiStatisticsSearchRequest.builder() + .env(envId.toString()) + .queryStart(now.minusDays(2)) + .queryEnd(now) + .build(); + // when + var result = sut.loadErrorsStatistics(searchRequest); + // then + assertThat(result.getErrorBreakdowns()).hasSize(2); + var errorBreakdown0 = result.getErrorBreakdowns().get(0); + var errorBreakdown1 = result.getErrorBreakdowns().get(1); + assertThat(errorBreakdown0.getDate()).isBefore(errorBreakdown1.getDate()); + assertThat(errorBreakdown0.getErrors()).containsEntry(401, 4L).containsEntry(500, 4L); + assertThat(errorBreakdown1.getErrors()).containsEntry(401, 6L).containsEntry(500, 6L); + } + + @Test + void givenErrorApiActivityLogs_whenLoadErrorsStatisticsByBuyerId_thenReturnsErrorStatistics() { + // given + var envId = UUID.randomUUID(); + var now = ZonedDateTime.parse(NOW_WITH_TIMEZONE); + addApiLogActivity(envId.toString(), createPayloads(toUTC(now))); + var searchRequest = + ApiStatisticsSearchRequest.builder() + .env(envId.toString()) + .queryStart(now.minusDays(2)) + .queryEnd(now) + .buyerId(BUYER_ID_1) + .build(); + // when + var result = sut.loadErrorsStatistics(searchRequest); + // then + assertThat(result.getErrorBreakdowns()).hasSize(2); + assertThat(result.getErrorBreakdowns().get(0).getErrors()).containsEntry(401, 4L); + assertThat(result.getErrorBreakdowns().get(1).getErrors()).containsEntry(401, 6L); + } + + @Test + void givenNoLogsForEnv_whenLoadMostPopularEndpointStatistics_thenEmptyResult() { + // given + var envId = UUID.randomUUID(); + var now = ZonedDateTime.parse(NOW_WITH_TIMEZONE); + var searchRequest = + ApiStatisticsSearchRequest.builder() + .env(envId.toString()) + .queryStart(now.minusDays(2)) + .queryEnd(now) + .build(); + // when + var result = sut.loadMostPopularEndpointStatistics(searchRequest); + // then + assertThat(result.getEndpointUsages()).isEmpty(); + } + + @Test + void givenNoLogsForEnv_whenLoadMostPopularEndpointStatisticsOutOfTheTimeRage_thenEmptyResult() { + // given + var envId = UUID.randomUUID(); + var now = ZonedDateTime.parse(NOW_WITH_TIMEZONE); + var payload = payloadForEndpointPopularity(toUTC(now)); + addApiLogActivity(envId.toString(), payload); + var searchRequest = + ApiStatisticsSearchRequest.builder() + .env(envId.toString()) + .queryStart(now.minusDays(4)) + .queryEnd(now.minusDays(2)) + .build(); + // when + var result = sut.loadMostPopularEndpointStatistics(searchRequest); + // then + assertThat(result.getEndpointUsages()).isEmpty(); + } + + @Test + void givenApiActivityLogs_whenLoadMostPopularEndpointStatistics_thenReturnsEndpointsStatistics() { + // given + var envId = UUID.randomUUID(); + var now = ZonedDateTime.parse(NOW_WITH_TIMEZONE); + var payload = payloadForEndpointPopularity(toUTC(now)); + addApiLogActivity(envId.toString(), payload); + var searchRequest = + ApiStatisticsSearchRequest.builder() + .env(envId.toString()) + .queryStart(now.minusDays(2)) + .queryEnd(now) + .build(); + // when + var result = sut.loadMostPopularEndpointStatistics(searchRequest); + // then + assertThat(result.getEndpointUsages()).hasSize(7); + assertThat(result.getEndpointUsages().get(0).getMethod()).isEqualTo("GET"); + assertThat(result.getEndpointUsages().get(0).getEndpoint()) + .isEqualTo("/mefApi/sonata/product/0"); + assertThat(result.getEndpointUsages().get(0).getUsage()).isEqualTo(30); + assertThat(result.getEndpointUsages().get(0).getPopularity()).isEqualTo(30.0); + assertThat(result.getEndpointUsages().get(6).getMethod()).isEqualTo("DELETE"); + assertThat(result.getEndpointUsages().get(6).getEndpoint()) + .isEqualTo("/mefApi/sonata/product/6"); + assertThat(result.getEndpointUsages().get(6).getUsage()).isEqualTo(5); + assertThat(result.getEndpointUsages().get(6).getPopularity()).isEqualTo(5.0); + } + + @Test + void + givenApiActivityLogs_whenLoadMostPopularEndpointStatisticsByBuyerId_thenReturnsEndpointsStatistics() { + // given + var envId = UUID.randomUUID(); + var now = ZonedDateTime.parse(NOW_WITH_TIMEZONE); + var payload = payloadForEndpointPopularity(toUTC(now)); + addApiLogActivity(envId.toString(), payload); + var searchRequest = + ApiStatisticsSearchRequest.builder() + .env(envId.toString()) + .queryStart(now.minusDays(2)) + .queryEnd(now) + .buyerId(BUYER_ID_2) + .build(); + // when + var result = sut.loadMostPopularEndpointStatistics(searchRequest); + // then + assertThat(result.getEndpointUsages()).hasSize(1); + assertThat(result.getEndpointUsages().get(0).getMethod()).isEqualTo("GET"); + assertThat(result.getEndpointUsages().get(0).getEndpoint()) + .isEqualTo("/mefApi/sonata/product/9"); + assertThat(result.getEndpointUsages().get(0).getUsage()).isEqualTo(2); + assertThat(result.getEndpointUsages().get(0).getPopularity()).isEqualTo(2.0); + } + + private List payloadForEndpointPopularity(ZonedDateTime now) { + var payload = new ArrayList(); + payload.addAll( + PayloadBuilder.builder() + .method("GET") + .path0("/mefApi/sonata/product/0") + .path1("/hub/product/0") + .httpStatus(200) + .now(now) + .number(30) + .buyerId(BUYER_ID_1) + .build() + .createPayload()); + payload.addAll( + PayloadBuilder.builder() + .method("POST") + .path0("/mefApi/sonata/product/1") + .path1("/hub/product/1") + .httpStatus(200) + .now(now) + .number(20) + .buyerId(BUYER_ID_1) + .build() + .createPayload()); + payload.addAll( + PayloadBuilder.builder() + .method("DELETE") + .path0("/mefApi/sonata/product/2") + .path1("/hub/product/2") + .httpStatus(201) + .now(now) + .number(12) + .buyerId(BUYER_ID_1) + .build() + .createPayload()); + payload.addAll( + PayloadBuilder.builder() + .method("PUT") + .path0("/mefApi/sonata/product/3") + .path1("/hub/product/3") + .httpStatus(204) + .now(now) + .number(10) + .build() + .createPayload()); + payload.addAll( + PayloadBuilder.builder() + .method("GET") + .path0("/mefApi/sonata/product/4") + .path1("/hub/product/4") + .httpStatus(400) + .now(now) + .number(8) + .buyerId(BUYER_ID_1) + .build() + .createPayload()); + payload.addAll( + PayloadBuilder.builder() + .method("POST") + .path0("/mefApi/sonata/product/5") + .path1("/hub/product/5") + .httpStatus(401) + .now(now) + .number(6) + .buyerId(BUYER_ID_1) + .build() + .createPayload()); + payload.addAll( + PayloadBuilder.builder() + .method("DELETE") + .path0("/mefApi/sonata/product/6") + .path1("/hub/product/6") + .httpStatus(402) + .now(now) + .number(5) + .buyerId(BUYER_ID_1) + .build() + .createPayload()); + payload.addAll( + PayloadBuilder.builder() + .method("PUT") + .path0("/mefApi/sonata/product/7") + .path1("/hub/product/7") + .httpStatus(404) + .now(now) + .number(4) + .buyerId(BUYER_ID_1) + .build() + .createPayload()); + payload.addAll( + PayloadBuilder.builder() + .method("GET") + .path0("/mefApi/sonata/product/8") + .path1("/hub/product/8") + .httpStatus(500) + .now(now) + .number(3) + .buyerId(BUYER_ID_1) + .build() + .createPayload()); + payload.addAll( + PayloadBuilder.builder() + .method("GET") + .path0("/mefApi/sonata/product/9") + .path1("/hub/product/9") + .httpStatus(500) + .now(now) + .number(2) + .buyerId(BUYER_ID_2) + .build() + .createPayload()); + return payload; + } + + private void addApiLogActivity(String envId, List payloads) { + payloads.forEach( + p -> { + clientAPIAuditLogEventHandler.onEvent( + envId, UUID.randomUUID().toString(), createEvent(p)); + }); + } + + private List createPayloads(ZonedDateTime now) { + var payload = new ArrayList(); + payload.addAll( + PayloadBuilder.builder() + .method("GET") + .path0("/mefApi/sonata/product/123") + .path1("/hub/product/123") + .httpStatus(200) + .now(now) + .number(10) + .buyerId(BUYER_ID_1) + .build() + .createPayload()); + payload.addAll( + PayloadBuilder.builder() + .method("GET") + .path0("/mefApi/sonata/product/234") + .path1("/hub/product/234") + .httpStatus(204) + .now(now) + .number(10) + .buyerId(BUYER_ID_1) + .build() + .createPayload()); + payload.addAll( + PayloadBuilder.builder() + .method("GET") + .path0("/mefApi/sonata/order/678") + .path1("/hub/order/678") + .httpStatus(401) + .now(now) + .number(10) + .buyerId(BUYER_ID_1) + .build() + .createPayload()); + payload.addAll( + PayloadBuilder.builder() + .method("GET") + .path0("/mefApi/sonata/product/789") + .path1("/hub/product/789") + .httpStatus(500) + .now(now) + .number(10) + .buyerId("buyer2") + .build() + .createPayload()); + return payload; + } + + private static ClientEvent createEvent(String json) { + var clientEvent = new ClientEvent(); + clientEvent.setEventType(ClientEventTypeEnum.CLIENT_API_AUDIT_LOG); + clientEvent.setClientId("127.0.1.1"); + clientEvent.setEventPayload(json); + return clientEvent; + } + + @lombok.Builder + public static class PayloadBuilder { + private String method; + private String path0; + private String path1; + private int httpStatus; + private ZonedDateTime now; + private int number; + private String buyerId; + + List createPayload() { + return IntStream.range(0, number) + .mapToObj( + operand -> { + ZonedDateTime date = now.minusHours(operand); + return createPayload( + method, path0, path1, httpStatus, date, date.plusSeconds(2), buyerId); + }) + .toList(); + } + + private String createPayload( + String method, + String path0, + String path1, + Integer httpStatus, + ZonedDateTime createdAt0, + ZonedDateTime createdAt1, + String buyerId) { + var requestId = UUID.randomUUID(); + var apiActivityLog0 = + getApiActivityLog0(method, path0, httpStatus, createdAt0, requestId, buyerId); + var apiActivityLog1 = + getApiActivityLog1(method, path1, httpStatus, createdAt1, requestId, buyerId); + return JsonToolkit.toJson(List.of(apiActivityLog0, apiActivityLog1)); + } + + private ApiActivityLog getApiActivityLog0( + String method, + String path0, + Integer httpStatus, + ZonedDateTime createdAt0, + UUID requestId, + String buyerId) { + var apiActivityLog0 = new ApiActivityLog(); + apiActivityLog0.setRequestId(requestId.toString()); + apiActivityLog0.setCallSeq(0); + apiActivityLog0.setUri("http://localhost:8888/mef.sonata"); + apiActivityLog0.setMethod(method); + apiActivityLog0.setPath(path0); + apiActivityLog0.setHttpStatusCode(httpStatus); + apiActivityLog0.setCreatedAt(createdAt0); + apiActivityLog0.setBuyer(buyerId); + return apiActivityLog0; + } + + private ApiActivityLog getApiActivityLog1( + String method, + String path1, + Integer httpStatus, + ZonedDateTime createdAt1, + UUID requestId, + String buyerId) { + var apiActivityLog1 = new ApiActivityLog(); + apiActivityLog1.setRequestId(requestId.toString()); + apiActivityLog1.setCallSeq(1); + apiActivityLog1.setUri("http://localhost:8888/mef.sonata"); + apiActivityLog1.setMethod(method); + apiActivityLog1.setPath(path1); + apiActivityLog1.setHttpStatusCode(httpStatus); + apiActivityLog1.setCreatedAt(createdAt1); + apiActivityLog1.setBuyer(buyerId); + return apiActivityLog1; + } + } +} diff --git a/kraken-java-sdk/kraken-java-sdk-core/src/main/java/com/consoleconnect/kraken/operator/core/repo/ApiActivityLogRepository.java b/kraken-java-sdk/kraken-java-sdk-core/src/main/java/com/consoleconnect/kraken/operator/core/repo/ApiActivityLogRepository.java index ad245d20..f5a79125 100644 --- a/kraken-java-sdk/kraken-java-sdk-core/src/main/java/com/consoleconnect/kraken/operator/core/repo/ApiActivityLogRepository.java +++ b/kraken-java-sdk/kraken-java-sdk-core/src/main/java/com/consoleconnect/kraken/operator/core/repo/ApiActivityLogRepository.java @@ -10,7 +10,9 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; public interface ApiActivityLogRepository extends PagingAndSortingRepository, @@ -25,4 +27,21 @@ public interface ApiActivityLogRepository Page findAllBySyncStatusAndCreatedAtBefore( SyncStatusEnum syncStatus, ZonedDateTime createdAt, Pageable pageable); + + @Query( + "SELECT e.path, e.method , COUNT(e) FROM #{#entityName} e " + + "WHERE e.env = :env " + + "AND e.callSeq = :callSeq " + + "AND e.createdAt BETWEEN :startDate AND :endDate " + + "AND ( (:buyer) is null or e.buyer = :buyer )" + + "GROUP BY e.path, e.method " + + "ORDER BY COUNT(e) DESC " + + "LIMIT :limit ") + List findTopEndpoints( + @Param("env") String env, + @Param("startDate") ZonedDateTime startDate, + @Param("endDate") ZonedDateTime endDate, + @Param("callSeq") String callSeq, + @Param("buyer") String buyer, + @Param("limit") int limit); } diff --git a/kraken-java-sdk/kraken-java-sdk-core/src/main/java/com/consoleconnect/kraken/operator/core/request/ApiStatisticsSearchRequest.java b/kraken-java-sdk/kraken-java-sdk-core/src/main/java/com/consoleconnect/kraken/operator/core/request/ApiStatisticsSearchRequest.java new file mode 100644 index 00000000..dc0125a9 --- /dev/null +++ b/kraken-java-sdk/kraken-java-sdk-core/src/main/java/com/consoleconnect/kraken/operator/core/request/ApiStatisticsSearchRequest.java @@ -0,0 +1,14 @@ +package com.consoleconnect.kraken.operator.core.request; + +import java.time.ZonedDateTime; +import lombok.Builder; +import lombok.Data; + +@Builder +@Data +public class ApiStatisticsSearchRequest { + String env; + ZonedDateTime queryStart; + ZonedDateTime queryEnd; + String buyerId; +} From af461ff8789f725965b3856c28e761b7708ed6c7 Mon Sep 17 00:00:00 2001 From: xuelianhan007 <148412906+xuelianhan007@users.noreply.github.com> Date: Wed, 6 Nov 2024 09:58:01 +0800 Subject: [PATCH 3/5] fix(sdk): always add seller information in response (#72) --- .../expected-9-quote.uni.add.sync.json | 2 +- .../api-target.quote.eline.add.sync.yaml | 2 +- .../api-target.quote.uni.add.sync.yaml | 2 +- .../api-target.quote.eline.add.sync.yaml | 2 +- .../api-target.quote.uni.add.sync.yaml | 2 +- .../src/main/resources/mef-sonata/product.yaml | 1 + .../template-upgrade/release.1.2.0.yaml | 18 ------------------ .../template-upgrade/release.1.2.1.yaml | 15 --------------- .../template-upgrade/release.1.2.2.yaml | 16 ---------------- .../template-upgrade/release.1.2.3.yaml | 15 --------------- .../template-upgrade/release.1.2.4.yaml | 15 --------------- .../template-upgrade/release.1.2.5.yaml | 15 --------------- .../template-upgrade/release.1.2.6.yaml | 15 --------------- .../template-upgrade/release.1.3.0.yaml | 16 ---------------- .../template-upgrade/release.1.5.2.yaml | 13 +++++++++++++ 15 files changed, 19 insertions(+), 130 deletions(-) delete mode 100644 kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.0.yaml delete mode 100644 kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.1.yaml delete mode 100644 kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.2.yaml delete mode 100644 kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.3.yaml delete mode 100644 kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.4.yaml delete mode 100644 kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.5.yaml delete mode 100644 kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.6.yaml delete mode 100644 kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.3.0.yaml create mode 100644 kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.5.2.yaml diff --git a/kraken-app/kraken-app-hub/src/test/resources/expected/expected-9-quote.uni.add.sync.json b/kraken-app/kraken-app-hub/src/test/resources/expected/expected-9-quote.uni.add.sync.json index 0ce6db17..cdde7155 100644 --- a/kraken-app/kraken-app-hub/src/test/resources/expected/expected-9-quote.uni.add.sync.json +++ b/kraken-app/kraken-app-hub/src/test/resources/expected/expected-9-quote.uni.add.sync.json @@ -1,7 +1,7 @@ { "id": "${entity.id}", "buyerRequestedQuoteLevel": "${mefRequestBody.buyerRequestedQuoteLevel}", - "relatedContactInformation": "${T(com.consoleconnect.kraken.operator.gateway.func.SpelFunc).appendSellerInformation('sellerContactInformation',env.seller.name,env.seller.emailAddress,env.seller.number,mefRequestBody.relatedContactInformation)}", + "relatedContactInformation": "${T(com.consoleconnect.kraken.operator.gateway.func.SpelFunc).appendSellerInformation('sellerContactInformation',env.seller.name,env.seller.emailAddress,env.seller.number,mefRequestBody[relatedContactInformation]?:'')}", "quoteItem": [ { "requestedQuoteItemTerm": "${mefRequestBody.quoteItem[0].requestedQuoteItemTerm}", diff --git a/kraken-app/kraken-app-hub/src/test/resources/mock/api-targets/api-target.quote.eline.add.sync.yaml b/kraken-app/kraken-app-hub/src/test/resources/mock/api-targets/api-target.quote.eline.add.sync.yaml index 985942ac..9c4e6f50 100644 --- a/kraken-app/kraken-app-hub/src/test/resources/mock/api-targets/api-target.quote.eline.add.sync.yaml +++ b/kraken-app/kraken-app-hub/src/test/resources/mock/api-targets/api-target.quote.eline.add.sync.yaml @@ -25,7 +25,7 @@ spec: { "id": "${entity.id}", "buyerRequestedQuoteLevel": "${mefRequestBody.buyerRequestedQuoteLevel}", - "relatedContactInformation": "${T(com.consoleconnect.kraken.operator.gateway.func.SpelFunc).appendSellerInformation('sellerContactInformation', env.seller.name, env.seller.emailAddress, env.seller.number, mefRequestBody.relatedContactInformation)}", + "relatedContactInformation": "${T(com.consoleconnect.kraken.operator.gateway.func.SpelFunc).appendSellerInformation('sellerContactInformation', env.seller.name, env.seller.emailAddress, env.seller.number, mefRequestBody[relatedContactInformation]?:'')}", "quoteItem": [{ "requestedQuoteItemTerm": "${mefRequestBody.quoteItem[0].requestedQuoteItemTerm}", "product": "${mefRequestBody.quoteItem[0].product}", diff --git a/kraken-app/kraken-app-hub/src/test/resources/mock/api-targets/api-target.quote.uni.add.sync.yaml b/kraken-app/kraken-app-hub/src/test/resources/mock/api-targets/api-target.quote.uni.add.sync.yaml index a6e386ca..d7d7ffca 100644 --- a/kraken-app/kraken-app-hub/src/test/resources/mock/api-targets/api-target.quote.uni.add.sync.yaml +++ b/kraken-app/kraken-app-hub/src/test/resources/mock/api-targets/api-target.quote.uni.add.sync.yaml @@ -26,7 +26,7 @@ spec: { "id": "${entity.id}", "buyerRequestedQuoteLevel": "${mefRequestBody.buyerRequestedQuoteLevel}", - "relatedContactInformation": "${T(com.consoleconnect.kraken.operator.gateway.func.SpelFunc).appendSellerInformation('sellerContactInformation', env.seller.name, env.seller.emailAddress, env.seller.number, mefRequestBody.relatedContactInformation)}", + "relatedContactInformation": "${T(com.consoleconnect.kraken.operator.gateway.func.SpelFunc).appendSellerInformation('sellerContactInformation', env.seller.name, env.seller.emailAddress, env.seller.number, mefRequestBody[relatedContactInformation]?:'')}", "quoteItem": [ { "requestedQuoteItemTerm": "${mefRequestBody.quoteItem[0].requestedQuoteItemTerm}", diff --git a/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/api-targets/api-target.quote.eline.add.sync.yaml b/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/api-targets/api-target.quote.eline.add.sync.yaml index 985942ac..9c4e6f50 100644 --- a/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/api-targets/api-target.quote.eline.add.sync.yaml +++ b/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/api-targets/api-target.quote.eline.add.sync.yaml @@ -25,7 +25,7 @@ spec: { "id": "${entity.id}", "buyerRequestedQuoteLevel": "${mefRequestBody.buyerRequestedQuoteLevel}", - "relatedContactInformation": "${T(com.consoleconnect.kraken.operator.gateway.func.SpelFunc).appendSellerInformation('sellerContactInformation', env.seller.name, env.seller.emailAddress, env.seller.number, mefRequestBody.relatedContactInformation)}", + "relatedContactInformation": "${T(com.consoleconnect.kraken.operator.gateway.func.SpelFunc).appendSellerInformation('sellerContactInformation', env.seller.name, env.seller.emailAddress, env.seller.number, mefRequestBody[relatedContactInformation]?:'')}", "quoteItem": [{ "requestedQuoteItemTerm": "${mefRequestBody.quoteItem[0].requestedQuoteItemTerm}", "product": "${mefRequestBody.quoteItem[0].product}", diff --git a/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/api-targets/api-target.quote.uni.add.sync.yaml b/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/api-targets/api-target.quote.uni.add.sync.yaml index 2fe2564f..6cd539f1 100644 --- a/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/api-targets/api-target.quote.uni.add.sync.yaml +++ b/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/api-targets/api-target.quote.uni.add.sync.yaml @@ -26,7 +26,7 @@ spec: { "id": "${entity.id}", "buyerRequestedQuoteLevel": "${mefRequestBody.buyerRequestedQuoteLevel}", - "relatedContactInformation": "${T(com.consoleconnect.kraken.operator.gateway.func.SpelFunc).appendSellerInformation('sellerContactInformation', env.seller.name, env.seller.emailAddress, env.seller.number, mefRequestBody.relatedContactInformation)}", + "relatedContactInformation": "${T(com.consoleconnect.kraken.operator.gateway.func.SpelFunc).appendSellerInformation('sellerContactInformation', env.seller.name, env.seller.emailAddress, env.seller.number, mefRequestBody[relatedContactInformation]?:'')}", "quoteItem": [ { "requestedQuoteItemTerm": "${mefRequestBody.quoteItem[0].requestedQuoteItemTerm}", diff --git a/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/product.yaml b/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/product.yaml index 19d9b05d..cec78f6c 100644 --- a/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/product.yaml +++ b/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/product.yaml @@ -97,6 +97,7 @@ spec: - classpath:/mef-sonata/mapping-matrix/mapping.matrix.inventory.uni.list.yaml - classpath:/mef-sonata/mapping-matrix/mapping.matrix.inventory.eline.list.yaml - classpath:/mef-sonata/template-upgrade/release.1.5.1.yaml + - classpath:/mef-sonata/template-upgrade/release.1.5.2.yaml templateUpgradePaths: diff --git a/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.0.yaml b/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.0.yaml deleted file mode 100644 index b64b7fbd..00000000 --- a/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.0.yaml +++ /dev/null @@ -1,18 +0,0 @@ ---- -kind: kraken.product.template-upgrade -apiVersion: v1 -metadata: - key: kraken.product.template-upgrade.1.2.0 - name: V1.2 - labels: - release-version: V1.2 - release-date: 2024-08-30 - description: | - fix issues: - upgrade all user mapper and target file - new feature: - support template upgrade - audit logs - - version: 1 - diff --git a/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.1.yaml b/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.1.yaml deleted file mode 100644 index ba3e24eb..00000000 --- a/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.1.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -kind: kraken.product.template-upgrade -apiVersion: v1 -metadata: - key: kraken.product.template-upgrade.1.2.1 - name: V1.2.1 - labels: - release-version: V1.2.1 - release-date: 2024-09-03 - description: | - fix issues: - upgrade quote sync add target mapper file - new feature: - - version: 1 diff --git a/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.2.yaml b/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.2.yaml deleted file mode 100644 index 765ae928..00000000 --- a/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.2.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -kind: kraken.product.template-upgrade -apiVersion: v1 -metadata: - key: kraken.product.template-upgrade.1.2.2 - name: V1.2.2 - labels: - release-version: V1.2.2 - release-date: 2024-09-03 - description: | - fix issues: - added target location in mef.sonata.api-target-mapper.quote.eline.add.sync - for target item @{{quoteItem.quoteItemPrice.price.dutyFreeAmount.value}} - new feature: - - version: 1 diff --git a/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.3.yaml b/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.3.yaml deleted file mode 100644 index 65ffe60a..00000000 --- a/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.3.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -kind: kraken.product.template-upgrade -apiVersion: v1 -metadata: - key: kraken.product.template-upgrade.1.2.3 - name: V1.2.3 - labels: - release-version: V1.2.3 - release-date: 2024-09-04 - description: | - add field mapping: - add metroId for address validation and address retrieve - new feature: - - version: 1 diff --git a/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.4.yaml b/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.4.yaml deleted file mode 100644 index 83769217..00000000 --- a/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.4.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -kind: kraken.product.template-upgrade -apiVersion: v1 -metadata: - key: kraken.product.template-upgrade.1.2.4 - name: V1.2.4 - labels: - release-version: V1.2.4 - release-date: 2024-09-10 - description: | - change field: - change type to @type in address validation - new feature: - - version: 1 diff --git a/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.5.yaml b/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.5.yaml deleted file mode 100644 index 4fe703d2..00000000 --- a/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.5.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -kind: kraken.product.template-upgrade -apiVersion: v1 -metadata: - key: kraken.product.template-upgrade.1.2.5 - name: V1.2.5 - labels: - release-version: V1.2.5 - release-date: 2024-09-11 - description: | - Add matrix validation: - add validation in address validation - new feature: - - version: 1 diff --git a/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.6.yaml b/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.6.yaml deleted file mode 100644 index 22e17054..00000000 --- a/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.2.6.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -kind: kraken.product.template-upgrade -apiVersion: v1 -metadata: - key: kraken.product.template-upgrade.1.2.6 - name: V1.2.6 - labels: - release-version: V1.2.6 - release-date: 2024-09-11 - description: | - Add matrix validation: - add validation in address validation - new feature: - - version: 1 diff --git a/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.3.0.yaml b/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.3.0.yaml deleted file mode 100644 index 3cfc6efe..00000000 --- a/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.3.0.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -kind: kraken.product.template-upgrade -apiVersion: v1 -metadata: - key: kraken.product.template-upgrade.1.3.0 - name: V1.3 - labels: - release-version: V1.3 - release-date: 2024-09-11 - description: | - New feature: - Add functions related to demo environment - - - version: 1 - diff --git a/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.5.2.yaml b/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.5.2.yaml new file mode 100644 index 00000000..26f7316a --- /dev/null +++ b/kraken-java-sdk/kraken-java-sdk-mef/src/main/resources/mef-sonata/template-upgrade/release.1.5.2.yaml @@ -0,0 +1,13 @@ +--- +kind: kraken.product.template-upgrade +apiVersion: v1 +metadata: + key: kraken.product.template-upgrade.1.5.2 + name: V1.5.2 + labels: + productSpec: grace + productVersion: V1.5.2 + publishDate: 2024-11-06 + description: | + Added seller information whenever the contact information exists or not in quote. + version: 2 \ No newline at end of file From 843d6ce8676ddab4edc03205f9b9b2386347deb3 Mon Sep 17 00:00:00 2001 From: kuangxiang20240501 <167289548+kuangxiang20240501@users.noreply.github.com> Date: Wed, 6 Nov 2024 10:05:35 +0800 Subject: [PATCH 4/5] fix(sdk): Update stage data plane upgrade check response structure (#71) * change template upgrade check response with flag rather than message * test case * add check test case for error env * modify newTemplate logic --- .../api/v3/TemplateUpgradeV3Controller.java | 4 +- .../dto/TemplateUpgradeCheckDTO.java | 13 ++++++ .../service/TemplateUpgradeService.java | 38 ++++++++-------- .../v2/TemplateUpgradeControllerTest.java | 45 +++++++++++++++++-- 4 files changed, 75 insertions(+), 25 deletions(-) create mode 100644 kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/TemplateUpgradeCheckDTO.java diff --git a/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/api/v3/TemplateUpgradeV3Controller.java b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/api/v3/TemplateUpgradeV3Controller.java index 789952b0..1a0c21d1 100644 --- a/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/api/v3/TemplateUpgradeV3Controller.java +++ b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/api/v3/TemplateUpgradeV3Controller.java @@ -138,7 +138,7 @@ public HttpResponse> listTemplateChangeLog( @Operation(summary = "stage environment upgrade check") @GetMapping("/stage-upgrade-check") - public HttpResponse> checkStageUpgradeCondition( + public HttpResponse checkStageUpgradeCondition( @PathVariable("productId") String productId, @RequestParam(value = "templateUpgradeId", required = false) String templateUpgradeId, @RequestParam String envId) { @@ -147,7 +147,7 @@ public HttpResponse> checkStageUpgradeCondition( @Operation(summary = "product environment upgrade check") @GetMapping("/production-upgrade-check") - public HttpResponse> checkProductionUpgradeCondition( + public HttpResponse checkProductionUpgradeCondition( @PathVariable("productId") String productId, @RequestParam(value = "templateUpgradeId", required = false) String templateUpgradeId, @RequestParam String envId) { diff --git a/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/TemplateUpgradeCheckDTO.java b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/TemplateUpgradeCheckDTO.java new file mode 100644 index 00000000..8f6e186d --- /dev/null +++ b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/dto/TemplateUpgradeCheckDTO.java @@ -0,0 +1,13 @@ +package com.consoleconnect.kraken.operator.controller.dto; + +import java.util.ArrayList; +import java.util.List; +import lombok.Data; + +@Data +public class TemplateUpgradeCheckDTO { + Boolean compatible; + Boolean mapperCompleted; + Boolean newerTemplate; + List errorMessages = new ArrayList<>(); +} diff --git a/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/service/TemplateUpgradeService.java b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/service/TemplateUpgradeService.java index 0aa537e3..81555330 100644 --- a/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/service/TemplateUpgradeService.java +++ b/kraken-java-sdk/kraken-java-sdk-controller/src/main/java/com/consoleconnect/kraken/operator/controller/service/TemplateUpgradeService.java @@ -1078,11 +1078,11 @@ private void checkIsLatestUpgrade(String currentTemplateUpgradeId) { } } - public List stageCheck(String templateUpgradeId, String stageEnvId) { - List resultList = new ArrayList<>(); + public TemplateUpgradeCheckDTO stageCheck(String templateUpgradeId, String stageEnvId) { + TemplateUpgradeCheckDTO templateUpgradeCheckDTO = new TemplateUpgradeCheckDTO(); Environment environment = environmentService.findOne(stageEnvId); if (!environment.getName().equalsIgnoreCase(EnvNameEnum.STAGE.name())) { - resultList.add("error environment: not stage environment"); + throw KrakenException.badRequest("error environment: not stage environment"); } List stageRunningMappers = productDeploymentService.listRunningApiMapperDeploymentV3(stageEnvId); @@ -1094,34 +1094,32 @@ public List stageCheck(String templateUpgradeId, String stageEnvId) { .filter(t -> runningMapperKeys.contains(t.getTargetMapperKey())) .anyMatch( t -> t.getMappingStatus().equalsIgnoreCase(MappingStatusEnum.INCOMPLETE.getDesc())); - if (existedInCompleted) { - resultList.add( - "Please adjust and complete the incomplete mapping use cases that will be upgraded to data plane."); - } + templateUpgradeCheckDTO.setMapperCompleted(!existedInCompleted); + boolean newTemplate = true; try { checkIsLatestUpgrade(templateUpgradeId); - } catch (Exception e) { - resultList.add(e.getMessage()); + newTemplate = false; + } finally { + templateUpgradeCheckDTO.setNewerTemplate(newTemplate); } - return resultList; + return templateUpgradeCheckDTO; } - public List productionCheck(String templateUpgradeId, String productionEnvId) { - List resultList = new ArrayList<>(); + public TemplateUpgradeCheckDTO productionCheck(String templateUpgradeId, String productionEnvId) { + TemplateUpgradeCheckDTO templateUpgradeCheckDTO = new TemplateUpgradeCheckDTO(); Environment environment = environmentService.findOne(productionEnvId); if (!environment.getName().equalsIgnoreCase(EnvNameEnum.PRODUCTION.name())) { - resultList.add("error environment: not production environment"); - } - if (!checkStageCompatibility(templateUpgradeId)) { - resultList.add( - "Kraken version running in stage data plane is not compatible with this mapping template. Please upgrade kraken in stage data plane and Start verification"); + throw KrakenException.badRequest("error environment: not production environment"); } + templateUpgradeCheckDTO.setCompatible(checkStageCompatibility(templateUpgradeId)); + boolean newTemplate = true; try { checkIsLatestUpgrade(templateUpgradeId); - } catch (Exception e) { - resultList.add(e.getMessage()); + newTemplate = false; + } finally { + templateUpgradeCheckDTO.setNewerTemplate(newTemplate); } - return resultList; + return templateUpgradeCheckDTO; } public boolean checkStageCompatibility(String templateUpgradeId) { diff --git a/kraken-java-sdk/kraken-java-sdk-controller/src/test/java/com/consoleconnect/kraken/operator/controller/v2/TemplateUpgradeControllerTest.java b/kraken-java-sdk/kraken-java-sdk-controller/src/test/java/com/consoleconnect/kraken/operator/controller/v2/TemplateUpgradeControllerTest.java index 61e9716c..d85eec1d 100644 --- a/kraken-java-sdk/kraken-java-sdk-controller/src/test/java/com/consoleconnect/kraken/operator/controller/v2/TemplateUpgradeControllerTest.java +++ b/kraken-java-sdk/kraken-java-sdk-controller/src/test/java/com/consoleconnect/kraken/operator/controller/v2/TemplateUpgradeControllerTest.java @@ -52,6 +52,7 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.web.reactive.server.WebTestClient; @@ -239,7 +240,26 @@ void givenStageUpgraded_whenCheckStageUpgrade_thenReturnData() { .build(TestContextConstants.PRODUCT_ID), body -> { assertThat(body, hasJsonPath("$.code", equalTo(200))); - assertThat(body, hasJsonPath("$.data", hasSize(0))); + assertThat(body, hasJsonPath("$.data.mapperCompleted", is(true))); + }); + } + + @Test + @Order(6) + void givenStageUpgradedAndErrorEnv_whenCheckStageUpgrade_thenReturnCode400() { + String url = "/v3/products/{productId}/template-upgrade/stage-upgrade-check"; + UnifiedAssetDto assetDto = + unifiedAssetService.findByKind(AssetKindEnum.PRODUCT_TEMPLATE_UPGRADE.getKind()).get(0); + testClientHelper.getAndVerify( + uriBuilder -> + uriBuilder + .path(url) + .queryParam("templateUpgradeId", assetDto.getId()) + .queryParam("envId", TestApplication.productionEnvId) + .build(TestContextConstants.PRODUCT_ID), + HttpStatus.BAD_REQUEST, + body -> { + assertThat(body, hasJsonPath("$.reason", containsString("not stage environment"))); }); } @@ -254,11 +274,30 @@ void givenProductionUpgraded_whenCheckProductionUpgrade_thenReturnData() { uriBuilder .path(url) .queryParam("templateUpgradeId", assetDto.getId()) - .queryParam("envId", TestApplication.envId) + .queryParam("envId", TestApplication.productionEnvId) .build(TestContextConstants.PRODUCT_ID), body -> { assertThat(body, hasJsonPath("$.code", equalTo(200))); - assertThat(body, hasJsonPath("$.data", hasSize(greaterThanOrEqualTo(1)))); + assertThat(body, hasJsonPath("$.data.compatible", is(true))); + }); + } + + @Test + @Order(6) + void givenProductionUpgradedAndErrorEnv_whenCheckProductionUpgrade_thenReturnCode400() { + String url = "/v3/products/{productId}/template-upgrade/production-upgrade-check"; + UnifiedAssetDto assetDto = + unifiedAssetService.findByKind(AssetKindEnum.PRODUCT_TEMPLATE_UPGRADE.getKind()).get(0); + testClientHelper.getAndVerify( + uriBuilder -> + uriBuilder + .path(url) + .queryParam("templateUpgradeId", assetDto.getId()) + .queryParam("envId", TestApplication.envId) + .build(TestContextConstants.PRODUCT_ID), + HttpStatus.BAD_REQUEST, + body -> { + assertThat(body, hasJsonPath("$.reason", containsString("not production environment"))); }); } } From 99e375866a9f28c422e2866f4c2dea75635fbde0 Mon Sep 17 00:00:00 2001 From: xuelianhan007 <148412906+xuelianhan007@users.noreply.github.com> Date: Wed, 6 Nov 2024 10:35:01 +0800 Subject: [PATCH 5/5] fix(sdk): fix tips of parameters in the path (#68) --- .../gateway/runner/MappingMatrixCheckerActionRunner.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kraken-java-sdk/kraken-java-sdk-gateway/src/main/java/com/consoleconnect/kraken/operator/gateway/runner/MappingMatrixCheckerActionRunner.java b/kraken-java-sdk/kraken-java-sdk-gateway/src/main/java/com/consoleconnect/kraken/operator/gateway/runner/MappingMatrixCheckerActionRunner.java index e0827c79..3dfd672c 100644 --- a/kraken-java-sdk/kraken-java-sdk-gateway/src/main/java/com/consoleconnect/kraken/operator/gateway/runner/MappingMatrixCheckerActionRunner.java +++ b/kraken-java-sdk/kraken-java-sdk-gateway/src/main/java/com/consoleconnect/kraken/operator/gateway/runner/MappingMatrixCheckerActionRunner.java @@ -147,7 +147,9 @@ public boolean check(DocumentContext documentContext, PathCheck pathCheck) { realValue = documentContext.read(pathCheck.path()); } catch (Exception e) { log.error("read json path error!"); - throwException(pathCheck, String.format("path %s not exist in request.", pathCheck.name)); + throwException( + pathCheck, + String.format("The parameter %s does not exist in the request.", pathCheck.name)); } if (realValue instanceof JSONArray array) { return array.stream().allMatch(value -> checkExpect(pathCheck, value));