Skip to content

Commit

Permalink
MARP-612 Storing number of downloads version
Browse files Browse the repository at this point in the history
  • Loading branch information
phhung-axonivy committed Aug 30, 2024
1 parent 6dc7bff commit fdd4875
Show file tree
Hide file tree
Showing 11 changed files with 198 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class RequestMappingConstants {
public static final String INSTALLATION_COUNT_BY_ID = "/installationcount/{id}";
public static final String PRODUCT_JSON_CONTENT_BY_PRODUCT_ID_AND_VERSION = "/productjsoncontent/{productId}/{version}";
public static final String VERSIONS_IN_DESIGNER = "/{id}/designerversions";
public static final String INSTALLATION_COUNT_BY_ID_AND_DESIGNER_VERSION = "/installationcount/{productId}/designer/{designerVersion}";
public static final String DESIGNER_INSTALLATION_BY_PRODUCT_ID_AND_DESIGNER_VERSION = "/installation/{productId}/designer/{designerVersion}";
public static final String DESIGNER_INSTALLATION_BY_PRODUCT_ID = "/installation/{productId}/designer";
public static final String CUSTOM_SORT = "custom-sort";
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
package com.axonivy.market.controller;

import static com.axonivy.market.constants.RequestMappingConstants.INSTALLATION_COUNT_BY_ID_AND_DESIGNER_VERSION;
import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT_JSON_CONTENT_BY_PRODUCT_ID_AND_VERSION;
import static com.axonivy.market.constants.RequestMappingConstants.VERSIONS_IN_DESIGNER;
import static com.axonivy.market.constants.RequestParamConstants.DESIGNER_VERSION;
import static com.axonivy.market.constants.RequestParamConstants.ID;
import static com.axonivy.market.constants.RequestParamConstants.PRODUCT_ID;
import static com.axonivy.market.constants.RequestParamConstants.SHOW_DEV_VERSION;
import static com.axonivy.market.constants.RequestParamConstants.VERSION;
import static com.axonivy.market.constants.RequestMappingConstants.BEST_MATCH_BY_ID_AND_VERSION;
import static com.axonivy.market.constants.RequestMappingConstants.BY_ID;
import static com.axonivy.market.constants.RequestMappingConstants.BY_ID_AND_VERSION;
import static com.axonivy.market.constants.RequestMappingConstants.DESIGNER_INSTALLATION_BY_PRODUCT_ID;
import static com.axonivy.market.constants.RequestMappingConstants.DESIGNER_INSTALLATION_BY_PRODUCT_ID_AND_DESIGNER_VERSION;
import static com.axonivy.market.constants.RequestMappingConstants.INSTALLATION_COUNT_BY_ID;
import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT_DETAILS;
import static com.axonivy.market.constants.RequestMappingConstants.PRODUCT_JSON_CONTENT_BY_PRODUCT_ID_AND_VERSION;
import static com.axonivy.market.constants.RequestMappingConstants.VERSIONS_BY_ID;
import static com.axonivy.market.constants.RequestMappingConstants.BEST_MATCH_BY_ID_AND_VERSION;
import static com.axonivy.market.constants.RequestMappingConstants.VERSIONS_IN_DESIGNER;
import java.util.List;
import java.util.Map;

import com.axonivy.market.model.DesignerInstallation;
import com.axonivy.market.service.ProductDesignerInstallationService;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
Expand Down Expand Up @@ -44,11 +48,15 @@
public class ProductDetailsController {
private final VersionService versionService;
private final ProductService productService;
private final ProductDesignerInstallationService productDesignerInstallationService;
private final ProductDetailModelAssembler detailModelAssembler;

public ProductDetailsController(VersionService versionService, ProductService productService, ProductDetailModelAssembler detailModelAssembler) {
public ProductDetailsController(VersionService versionService, ProductService productService,
ProductDesignerInstallationService productDesignerInstallationService,
ProductDetailModelAssembler detailModelAssembler) {
this.versionService = versionService;
this.productService = productService;
this.productDesignerInstallationService = productDesignerInstallationService;
this.detailModelAssembler = detailModelAssembler;
}

Expand Down Expand Up @@ -79,7 +87,7 @@ public ResponseEntity<Integer> syncInstallationCount(
return new ResponseEntity<>(result, HttpStatus.OK);
}

@PutMapping(INSTALLATION_COUNT_BY_ID_AND_DESIGNER_VERSION)
@PutMapping(DESIGNER_INSTALLATION_BY_PRODUCT_ID_AND_DESIGNER_VERSION)
@Operation(summary = "increase designer installation count by 1", description = "update designer installation count when click download product files by users")
public ResponseEntity<Boolean> increaseDesignerInstallationCount(
@PathVariable(PRODUCT_ID) @Parameter(description = "Product id (from meta.json)", example = "approval-decision-utils", in = ParameterIn.PATH) String productId,
Expand All @@ -88,6 +96,14 @@ public ResponseEntity<Boolean> increaseDesignerInstallationCount(
return new ResponseEntity<>(result, HttpStatus.OK);
}

@GetMapping(DESIGNER_INSTALLATION_BY_PRODUCT_ID)
@Operation(summary = "Get designer installation count by product id.", description = "get designer installation count by product id")
public ResponseEntity<List<DesignerInstallation>> getProductDesignerInstallationByProductId(
@PathVariable(PRODUCT_ID) @Parameter(description = "Product id (from meta.json)", example = "adobe-acrobat-connector", in = ParameterIn.PATH) String productId) {
List<DesignerInstallation> models = productDesignerInstallationService.findByProductId(productId);
return new ResponseEntity<>(models, HttpStatus.OK);
}

@GetMapping(BY_ID)
@Operation(summary = "increase installation count by 1", description = "update installation count when click download product files by users")
public ResponseEntity<ProductDetailModel> findProductDetails(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.axonivy.market.model;

import com.axonivy.market.entity.MavenArtifactModel;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

import java.util.List;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class DesignerInstallation {
@Schema(description = "Ivy designer version", example = "11.4.0")
private String designerVersion;
private int numberOfDownloads;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.axonivy.market.repository;

import com.axonivy.market.entity.Feedback;
import com.axonivy.market.entity.Product;
import com.axonivy.market.entity.ProductDesignerInstallation;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface ProductDesignerInstallationRepository extends MongoRepository<ProductDesignerInstallation, String>,
CustomProductRepository {

List<ProductDesignerInstallation> findByProductId(String productId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.axonivy.market.service;

import com.axonivy.market.entity.Product;
import com.axonivy.market.entity.ProductDesignerInstallation;
import com.axonivy.market.exceptions.model.InvalidParamException;
import com.axonivy.market.model.DesignerInstallation;
import com.axonivy.market.model.ProductCustomSortRequest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import java.util.List;

public interface ProductDesignerInstallationService {
List<DesignerInstallation> findByProductId(String productId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.axonivy.market.service.impl;

import com.axonivy.market.entity.ProductDesignerInstallation;
import com.axonivy.market.model.DesignerInstallation;
import com.axonivy.market.repository.ProductDesignerInstallationRepository;
import com.axonivy.market.service.ProductDesignerInstallationService;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Log4j2
@Service
public class ProductDesignerInstallationServiceImpl implements ProductDesignerInstallationService {

private final ProductDesignerInstallationRepository productDesignerInstallationRepository;

public ProductDesignerInstallationServiceImpl(ProductDesignerInstallationRepository productDesignerInstallationRepository) {
this.productDesignerInstallationRepository = productDesignerInstallationRepository;
}

@Override
public List<DesignerInstallation> findByProductId(String productId) {
List<DesignerInstallation> designerInstallations = new ArrayList<>();
List<ProductDesignerInstallation> productDesignerInstallations = productDesignerInstallationRepository.findByProductId(productId);
for(ProductDesignerInstallation productDesignerInstallation: productDesignerInstallations) {
DesignerInstallation designerInstallation = new DesignerInstallation(productDesignerInstallation.getDesignerVersion(), productDesignerInstallation.getInstallationCount());
designerInstallations.add(designerInstallation);
}
return designerInstallations;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import java.util.Map;
import com.axonivy.market.constants.RequestMappingConstants;
import com.axonivy.market.entity.productjsonfilecontent.ProductJsonContent;
import com.axonivy.market.model.DesignerInstallation;
import com.axonivy.market.service.ProductDesignerInstallationService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -40,6 +42,9 @@ class ProductDetailsControllerTest {
@Mock
VersionService versionService;

@Mock
ProductDesignerInstallationService productDesignerInstallationService;

@Mock
private ProductDetailModelAssembler detailModelAssembler;

Expand Down Expand Up @@ -210,4 +215,25 @@ private ProductJsonContent mockProductJsonContent() {

return jsonContent;
}

@Test
void testIncreaseDesignerInstallationCount() {
Mockito.when(productService.increaseInstallationCountForProductByDesignerVersion(Mockito.anyString(),
Mockito.anyString())).thenReturn(Boolean.TRUE);
ResponseEntity<Boolean> result = productDetailsController.increaseDesignerInstallationCount("portal", "11.4.0");
Assertions.assertEquals(HttpStatus.OK, result.getStatusCode());
Assertions.assertEquals(Boolean.TRUE, result.getBody());
}

@Test
void testGetProductDesignerInstallationByProductId() {
List<DesignerInstallation> models = List.of(new DesignerInstallation("11.4.0", 5));
Mockito.when(productDesignerInstallationService.findByProductId(Mockito.anyString())).thenReturn(models);
ResponseEntity<List<DesignerInstallation>> result = productDetailsController.getProductDesignerInstallationByProductId("portal");
Assertions.assertEquals(HttpStatus.OK, result.getStatusCode());
Assertions.assertEquals(1, Objects.requireNonNull(result.getBody()).size());
Assertions.assertEquals("11.4.0", result.getBody().get(0).getDesignerVersion());
Assertions.assertEquals(5, result.getBody().get(0).getNumberOfDownloads());
Assertions.assertEquals(models, result.getBody());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.axonivy.market.BaseSetup;
import com.axonivy.market.constants.MongoDBConstants;
import com.axonivy.market.entity.Product;
import com.axonivy.market.entity.ProductDesignerInstallation;
import com.mongodb.client.result.UpdateResult;
import org.bson.Document;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand All @@ -22,6 +24,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
Expand Down Expand Up @@ -142,4 +145,18 @@ void testUpdateInitialCount() {
repo.updateInitialCount(ID, initialCount);
verify(mongoTemplate).updateFirst(any(Query.class), eq(new Update().inc("InstallationCount", initialCount).set("SynchronizedInstallationCount", true)), eq(Product.class));
}

@Test
void testIncreaseInstallationCountForProductByDesignerVersion() {
String productId = "portal";
String designerVersion = "11.4.0";
ProductDesignerInstallation productDesignerInstallation = new ProductDesignerInstallation();
productDesignerInstallation.setProductId(productId);
productDesignerInstallation.setDesignerVersion(designerVersion);
productDesignerInstallation.setInstallationCount(5);
when(mongoTemplate.upsert(any(Query.class), any(Update.class), eq(ProductDesignerInstallation.class))).
thenReturn(UpdateResult.acknowledged(1L, 1L, null));
boolean success = repo.increaseInstallationCountForProductByDesignerVersion(productId, designerVersion);
assertTrue(success);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { ProductService } from '../../product.service';
import { provideHttpClient } from '@angular/common/http';
import { ElementRef } from '@angular/core';
import { ItemDropdown } from '../../../../shared/models/item-dropdown.model';
import { By } from '@angular/platform-browser';
import { MOCK_PRODUCT_DETAIL_BY_VERSION } from '../../../../shared/mocks/mock-data';

class MockElementRef implements ElementRef {
nativeElement = {
Expand All @@ -16,11 +18,11 @@ describe('ProductVersionActionComponent', () => {
let component: ProductDetailVersionActionComponent;
let fixture: ComponentFixture<ProductDetailVersionActionComponent>;
let productServiceMock: any;
let elementRef: MockElementRef;

beforeEach(() => {
productServiceMock = jasmine.createSpyObj('ProductService', [
'sendRequestToProductDetailVersionAPI' , 'sendRequestToUpdateInstallationCount'
'sendRequestToProductDetailVersionAPI' , 'sendRequestToUpdateInstallationCount',
'sendRequestToUpdateInstallationCountByDesignerVersion'
]);

TestBed.configureTestingModule({
Expand Down Expand Up @@ -190,4 +192,27 @@ describe('ProductVersionActionComponent', () => {
// Check if window.focus() was called
expect(mockWindowFocus).toHaveBeenCalled();
});

it('should render install designer button', () => {
component.product = MOCK_PRODUCT_DETAIL_BY_VERSION;
component.isDesignerEnvironment.set(true);
fixture.detectChanges();
const button = fixture.debugElement.query(By.css('.install-designer-button')).nativeElement;
expect(button.id).toEqual("install-button");
expect(button.getAttribute('product-id')).toEqual("cronjob");

productServiceMock.sendRequestToUpdateInstallationCount.and.returnValue(
of(1)
);
productServiceMock.sendRequestToUpdateInstallationCountByDesignerVersion.and.returnValue(
of()
);

// When calling the button.click(), we got a error 'install() is not define', this method is defined in the ivy designer
// So the test will fail
// Call the method in the component instead
component.onUpdateInstallationCountForDesigner();
expect(productServiceMock.sendRequestToUpdateInstallationCount).toHaveBeenCalledWith(component.productId);
expect(productServiceMock.sendRequestToUpdateInstallationCountByDesignerVersion).toHaveBeenCalled();
});
});
14 changes: 14 additions & 0 deletions marketplace-ui/src/app/modules/product/product.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,4 +242,18 @@ describe('ProductService', () => {
expect(req.request.headers.get('X-Requested-By')).toBe('ivy');
req.flush(['10.0.2', '10.0.1', '10.0.0']);
});

it('should call sendRequestToUpdateInstallationCountByDesignerVersion and return true', () => {
const productId = 'google-maps-connector';
const designerVersion = '11.4.0';

service.sendRequestToUpdateInstallationCountByDesignerVersion(designerVersion, productId).subscribe(response => {
expect(response).toBeTruthy();
});

const req = httpMock.expectOne(`api/product-details/installation/${productId}/designer/${designerVersion}`);
expect(req.request.method).toBe('PUT');
expect(req.request.headers.get('X-Requested-By')).toBe('ivy');
req.flush(true);
});
});
4 changes: 2 additions & 2 deletions marketplace-ui/src/app/modules/product/product.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ export class ProductService {
designerVersion: string,
productId: string
) {
const url = `api/product-details/installationcount/${productId}/designer/${designerVersion}`;
return this.httpClient.put<boolean>(url, {
const url = `api/product-details/installation/${productId}/designer/${designerVersion}`;
return this.httpClient.put<boolean>(url, null, {
headers: { 'X-Requested-By': 'ivy' }
});
}
Expand Down

0 comments on commit fdd4875

Please sign in to comment.