Skip to content

Commit

Permalink
Merge branch 'feature/MARP-394-MP-API-to-fetch-all-artifacts-and-sear…
Browse files Browse the repository at this point in the history
…ch' of https://github.com/axonivy-market/marketplace-ui into feature/MARP-394-MP-API-to-fetch-all-artifacts-and-search

# Conflicts:
#	src/app/modules/product/product.component.ts
#	src/app/modules/product/product.service.spec.ts
#	src/app/modules/product/product.service.ts
#	src/app/shared/mocks/mock-data.ts
#	src/app/shared/mocks/mock-services.ts
  • Loading branch information
Hoan Nguyen committed Jun 13, 2024
2 parents 9137c18 + 7ed868f commit ceb2a30
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 37 deletions.
2 changes: 1 addition & 1 deletion src/app/core/interceptors/api.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const apiInterceptor: HttpInterceptorFn = (req, next) => {
return next(req);
}
let requestURL = req.url;
let apiURL = environment.apiUrl;
const apiURL = environment.apiUrl;
if (!requestURL.startsWith(apiURL)) {
requestURL = `${apiURL}/${req.url}`;
}
Expand Down
5 changes: 5 additions & 0 deletions src/app/modules/product/product.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,9 @@ describe('ProductComponent', () => {
expect(product.name.toLowerCase()).toContain(productName);
});
}));

it('setupIntersectionObserver should not trigger when init page', () => {
component.ngAfterViewInit();
expect(component.criteria.nextPageHref).toBeUndefined();
});
});
26 changes: 13 additions & 13 deletions src/app/modules/product/product.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ export class ProductComponent implements AfterViewInit, OnDestroy {
type: FilterType.All_TYPES,
sort: SortType.POPULARITY
};
links!: Link;
page!: Page;
responseLink!: Link;
responsePage!: Page;

productService = inject(ProductService);
themeService = inject(ThemeService);
Expand Down Expand Up @@ -85,7 +85,7 @@ export class ProductComponent implements AfterViewInit, OnDestroy {
onFilterChange(filterType: FilterType) {
this.criteria = {
...this.criteria,
nextPageHref: undefined,
nextPageHref: '',
type: filterType
};
this.loadProductItems(true);
Expand All @@ -94,7 +94,7 @@ export class ProductComponent implements AfterViewInit, OnDestroy {
onSortChange(sortType: SortType) {
this.criteria = {
...this.criteria,
nextPageHref: undefined,
nextPageHref: '',
sort: sortType
};
this.loadProductItems(true);
Expand All @@ -104,27 +104,27 @@ export class ProductComponent implements AfterViewInit, OnDestroy {
this.searchTextChanged.next(searchString);
}

loadProductItems(shouldCleanData?: boolean) {
loadProductItems(shouldCleanData = false) {
this.subscriptions.push(
this.productService.findProductsByCriteria(this.criteria).subscribe((response: ProductApiResponse) => {
let newProducts = response._embedded.products as Product[];
const newProducts = response._embedded.products;
if (shouldCleanData) {
this.products.set(newProducts);
} else {
this.products.update(existingProducts => existingProducts.concat(newProducts));
}
this.links = response._links;
this.page = response.page;
this.responseLink = response._links;
this.responsePage = response.page;
})
);
}

setupIntersectionObserver() {
const options = { root: null, rootMargin: '0px', threshold: 0.1 };
const observer = new IntersectionObserver((entries) => {
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting && this.hasMore()) {
this.criteria.nextPageHref = this.links?.next?.href;
this.criteria.nextPageHref = this.responseLink?.next?.href;
this.loadProductItems();
}
});
Expand All @@ -134,11 +134,11 @@ export class ProductComponent implements AfterViewInit, OnDestroy {
}

hasMore() {
if (!this.page || !this.links) {
if (!this.responsePage || !this.responseLink) {
return false;
}
return this.page.number < this.page.totalPages
&& this.links?.next != undefined;
return this.responsePage.number < this.responsePage.totalPages
&& this.responseLink?.next !== undefined;
}

ngOnDestroy(): void {
Expand Down
22 changes: 18 additions & 4 deletions src/app/modules/product/product.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { MOCK_PRODUCTS } from '../../shared/mocks/mock-data';
import { Criteria } from '../../shared/models/criteria.model';
import { ProductService } from './product.service';
import { Product } from '../../shared/models/product.model';
import { catchError } from 'rxjs';

const PRODUCT_ID = 'amazon-comprehend';
const NOT_EXIST_ID = 'undefined';
Expand Down Expand Up @@ -48,7 +49,7 @@ describe('ProductService', () => {
});

it('findProductsByCriteria with should return products properly', () => {
const searchString = 'amazon-comprehend';
const searchString = 'Amazon Comprehend';
const criteria: Criteria = {
search: searchString,
sort: SortType.ALPHABETICALLY,
Expand All @@ -68,7 +69,7 @@ describe('ProductService', () => {
});
});

it('getProductByCriteria with empty searchString', () => {
it('findProductsByCriteria with empty searchString', () => {
const criteria: Criteria = {
search: '',
sort: null,
Expand All @@ -79,7 +80,7 @@ describe('ProductService', () => {
});
});

it('getProductByCriteria with popularity order', () => {
it('findProductsByCriteria with popularity order', () => {
const criteria: Criteria = {
search: '',
sort: SortType.POPULARITY,
Expand All @@ -101,7 +102,7 @@ describe('ProductService', () => {
});
});

it('getProductByCriteria with default sort', () => {
it('findProductsByCriteria with default sort', () => {
const criteria: Criteria = {
search: '',
sort: SortType.RECENT,
Expand All @@ -111,4 +112,17 @@ describe('ProductService', () => {
expect(response._embedded.products.length).toEqual(products.length);
});
});

it('findProductsByCriteria by next page url', () => {
const criteria: Criteria = {
nextPageHref: 'http://localhost:8080/marketplace-service/api/product?type=all&page=1&size=20',
search: '',
sort: SortType.RECENT,
type: FilterType.All_TYPES
};
service.findProductsByCriteria(criteria).subscribe(response => {
expect(response._embedded.products.length).toEqual(0);
expect(response.page.number).toEqual(1);
});
});
});
21 changes: 4 additions & 17 deletions src/app/modules/product/product.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { Observable, catchError, map, of, throwError } from 'rxjs';
import { Observable, of } from 'rxjs';
import { MOCK_PRODUCTS } from '../../shared/mocks/mock-data';
import { Criteria } from '../../shared/models/criteria.model';
import { Product } from '../../shared/models/product.model';
Expand All @@ -24,25 +24,12 @@ export class ProductService {
.set(RequestParam.SORT, `${criteria.sort}`)
.set(RequestParam.KEYWORD, `${criteria.search}`);
}
return this.httpClient.get<ProductApiResponse>(requestURL, { params: requestParams })
.pipe(
catchError(this.handleError)
);
}

private handleError(error: HttpErrorResponse) {
let errorMessage = 'An unknown error occurred!';
if (error.error instanceof ErrorEvent) {
errorMessage = `Error: ${error.error.message}`;
} else {
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
}
return throwError(() => new Error(errorMessage));
return this.httpClient.get<ProductApiResponse>(requestURL, { params: requestParams });
}

// TODO MARP-358
getProductById(productId: string): Observable<Product> {
let products = MOCK_PRODUCTS._embedded.products as Product[];
const products = MOCK_PRODUCTS._embedded.products;
const product = products.find(p => p.id === productId);
if (product) {
return of(product);
Expand Down
20 changes: 20 additions & 0 deletions src/app/shared/mocks/mock-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,23 @@ export const MOCK_PRODUCTS_FILTER_CONNECTOR = {
}
} as ProductApiResponse;


export const MOCK_PRODUCTS_NEXT_PAGE = {
_embedded: {
products: []
},
_links: {
first: {
href: "http://localhost:8080/marketplace-service/api/product?type=all&page=0&size=20"
},
self: {
href: "http://localhost:8080/marketplace-service/api/product?type=all&page=1&size=20"
}
},
page: {
size: 20,
totalElements: 1,
totalPages: 1,
number: 1
}
} as ProductApiResponse;
7 changes: 5 additions & 2 deletions src/app/shared/mocks/mock-services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { Observable, of } from 'rxjs';
import { Product } from '../models/product.model';
import { Criteria } from '../models/criteria.model';
import { FilterType } from '../enums/filter-type.enum';
import { MOCK_PRODUCTS, MOCK_PRODUCTS_FILTER_CONNECTOR } from './mock-data';
import { MOCK_PRODUCTS, MOCK_PRODUCTS_FILTER_CONNECTOR, MOCK_PRODUCTS_NEXT_PAGE } from './mock-data';
import { ProductApiResponse } from '../models/apis/product-response.model';
import { ProductApiResponse } from '../models/apis/product-response.model';

const products = MOCK_PRODUCTS._embedded.products as Product[];
Expand All @@ -14,7 +15,9 @@ export class MockProductService {

findProductsByCriteria(criteria: Criteria): Observable<ProductApiResponse> {
let response = MOCK_PRODUCTS;
if (criteria.type == FilterType.CONNECTORS) {
if (criteria.nextPageHref) {
response = MOCK_PRODUCTS_NEXT_PAGE;
} else if (criteria.type == FilterType.CONNECTORS) {
response = MOCK_PRODUCTS_FILTER_CONNECTOR;
}
return of(response);
Expand Down

0 comments on commit ceb2a30

Please sign in to comment.