Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[angular] rework infinite-scroll based on response headers #25068

Merged
merged 1 commit into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,13 @@ export const createRequestOption = (req?: any): HttpParams => {
let options: HttpParams = new HttpParams();

if (req) {
Object.keys(req).forEach(key => {
if (key !== 'sort' && req[key] !== undefined) {
Object.entries(req).forEach(([key, val]) => {
if (val !== undefined && val !== null) {
for (const value of [].concat(req[key]).filter(v => v !== '')) {
options = options.append(key, value);
}
}
});

if (req.sort) {
req.sort.forEach((val: string) => {
options = options.append('sort', val);
});
}
}

return options;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { inject, TestBed } from '@angular/core/testing';
import { ParseLinks } from './parse-links.service';

describe('Parse links service test', () => {
describe('Parse Links Service Test', () => {
describe('parse', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [ParseLinks],
Expand Down Expand Up @@ -51,4 +51,34 @@ describe('Parse links service test', () => {
expect(service.parse(' </api/audits?page=0&size=20>; rel="last",</api/audits?page=0&size=20>; rel="first"')).toEqual(links);
}));
});
describe('parseAll', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [ParseLinks],
});
});

it('should throw an error when passed an empty string', inject([ParseLinks], (service: ParseLinks) => {
expect(function () {
service.parseAll('');
}).toThrow(new Error('input must not be of zero length'));
}));

it('should throw an error when passed without comma', inject([ParseLinks], (service: ParseLinks) => {
expect(function () {
service.parseAll('test');
}).toThrow(new Error('section could not be split on ";"'));
}));

it('should throw an error when passed without semicolon', inject([ParseLinks], (service: ParseLinks) => {
expect(function () {
service.parseAll('test,test2');
}).toThrow(new Error('section could not be split on ";"'));
}));

it('should return links when headers are passed', inject([ParseLinks], (service: ParseLinks) => {
const links = { last: { page: '0', size: '20' }, first: { page: '0', size: '20' } };
expect(service.parseAll(' </api/audits?page=0&size=20>; rel="last",</api/audits?page=0&size=20>; rel="first"')).toEqual(links);
}));
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -28,41 +28,50 @@ export class ParseLinks {
/**
* Method to parse the links
*/
parse(header: string): { [key: string]: number } {
parseAll(header: string): { [key: string]: { [key: string]: string | undefined } | undefined } {
if (header.length === 0) {
throw new Error('input must not be of zero length');
}

// Split parts by comma
const parts: string[] = header.split(',');
const links: { [key: string]: number } = {};

// Parse each part into a named link
parts.forEach(p => {
const section: string[] = p.split(';');
return Object.fromEntries(
parts.map(p => {
const section: string[] = p.split(';');

if (section.length !== 2) {
throw new Error('section could not be split on ";"');
}
if (section.length !== 2) {
throw new Error('section could not be split on ";"');
}

const url: string = section[0].replace(/<(.*)>/, '$1').trim(); // NOSONAR
const queryString: { [key: string]: string | undefined } = {};
const url: string = section[0].replace(/<(.*)>/, '$1').trim(); // NOSONAR
const queryString: { [key: string]: string } = {};

url.replace(
/([^?=&]+)(=([^&]*))?/g,
(_$0: string, $1: string | undefined, _$2: string | undefined, $3: string | undefined) => {
if ($1 !== undefined) {
queryString[$1] = $3;
url.replace(/([^?=&]+)(=([^&]*))?/g, (_$0: string, $1: string | undefined, _$2: string | undefined, $3: string | undefined) => {
if ($1 !== undefined && $3 !== undefined) {
queryString[$1] = decodeURIComponent($3);
}
return $3 ?? '';
}
);
});

if (queryString.page !== undefined) {
const name: string = section[1].replace(/rel="(.*)"/, '$1').trim();
links[name] = parseInt(queryString.page, 10);
return [name, queryString];
}),
);
}

/**
* Method to parse the links
*/
parse(header: string): { [key: string]: number } {
const sections = this.parseAll(header);
const links: { [key: string]: number } = {};
for (const [name, queryParams] of Object.entries(sections)) {
if (queryParams?.page !== undefined) {
links[name] = parseInt(queryParams.page, 10);
}
});
}
return links;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
<th scope="col"></th>
</tr>
</thead>
<tbody<% if (paginationInfiniteScroll) { %> infinite-scroll (scrolled)="loadPage(page + 1)" [infiniteScrollDisabled]="page - 1 >= links['last']" [infiniteScrollDistance]="0"<% } %>>
<tbody<% if (paginationInfiniteScroll) { %> infinite-scroll (scrolled)="loadNextPage()" [infiniteScrollDisabled]="!hasMorePage()" [infiniteScrollDistance]="0"<% } %>>
@for (<%= entityInstance %> of <%= entityInstancePlural %>; track track<%= primaryKey.nameCapitalized %>) {
<tr data-cy="entityTable">
<%_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
const entityArrayOptionalChainSymbol = '?.';
const order = 'desc';
const testEntityPrimaryKey = this.generateTestEntityPrimaryKey(primaryKey, 0);
const testEntityPrimaryKey2 = this.generateTestEntityPrimaryKey(primaryKey, 1);
_%>

import {
Expand Down Expand Up @@ -103,16 +104,29 @@ describe('<%= entityAngularName %> Management Component', () => {
service = TestBed.inject(<%= entityAngularName %>Service);
routerNavigateSpy = jest.spyOn(comp.router, 'navigate');

const headers = new HttpHeaders();
jest.spyOn(service, 'query').mockReturnValue(
jest
.spyOn(service, 'query')
.mockReturnValueOnce(
of(
new HttpResponse({
body: [<%- testEntityPrimaryKey %>],
headers,
})
)
);
});
new HttpResponse({
body: [<%- testEntityPrimaryKey %>],
headers: new HttpHeaders({
link: '<http://localhost/api/foo?page=1&size=20>; rel="next"',
}),
}),
),
)
.mockReturnValueOnce(
of(
new HttpResponse({
body: [<%- testEntityPrimaryKey2 %>],
headers: new HttpHeaders({
link: '<http://localhost/api/foo?page=0&size=20>; rel="prev",<http://localhost/api/foo?page=2&size=20>; rel="next"',
}),
}),
),
);
});

it('Should call load all on init', () => {
// WHEN
Expand Down Expand Up @@ -145,7 +159,7 @@ describe('<%= entityAngularName %> Management Component', () => {
})
}));
});
<%_ if (!paginationNo) { _%>
<%_ if (paginationPagination) { _%>

it('should load a page', () => {
// WHEN
Expand All @@ -154,6 +168,7 @@ describe('<%= entityAngularName %> Management Component', () => {
// THEN
expect(routerNavigateSpy).toHaveBeenCalled();
});
<%_ } _%>

it('should calculate the sort attribute for an id', () => {
// WHEN
Expand All @@ -162,30 +177,29 @@ describe('<%= entityAngularName %> Management Component', () => {
// THEN
expect(service.query).toHaveBeenLastCalledWith(expect.objectContaining({ sort: ['<%- primaryKey.fields.map(field => field.fieldName).join(',') %>,<%= order %>'] }));
});
<%_ if (paginationInfiniteScroll) { _%>

it('should infinite scroll', () => {
// GIVEN
comp.loadNextPage();
comp.loadNextPage();
comp.loadNextPage();

// THEN
expect(service.query).toHaveBeenCalledTimes(3);
expect(service.query).toHaveBeenNthCalledWith(2, expect.objectContaining({ page: '1' }));
expect(service.query).toHaveBeenLastCalledWith(expect.objectContaining({ page: '2' }));
});
<%_ } _%>
<%_ if (jpaMetamodelFiltering && paginationPagination) { _%>

<%_ if (paginationInfiniteScroll) { _%>

it('should re-initialize the page', () => {
// WHEN
comp.loadPage(1);
comp.reset();

// THEN
expect(comp.page).toEqual(1);
expect(service.query).toHaveBeenCalledTimes(2);
expect(comp.<%= entityInstancePlural %><%= entityArrayOptionalChainSymbol %>[0]).toEqual(expect.objectContaining(<%- testEntityPrimaryKey %>));
});
<%_ } _%>
<%_ if (jpaMetamodelFiltering && paginationPagination) { _%>

it('should calculate the filter attribute', () => {
// WHEN
comp.ngOnInit();
it('should calculate the filter attribute', () => {
// WHEN
comp.ngOnInit();

// THEN
expect(service.query).toHaveBeenLastCalledWith(expect.objectContaining({ 'someId.in': ['dc4279ea-cfb9-11ec-9d64-0242ac120002'] }));
});
<%_ } _%>
// THEN
expect(service.query).toHaveBeenLastCalledWith(expect.objectContaining({ 'someId.in': ['dc4279ea-cfb9-11ec-9d64-0242ac120002'] }));
});
<%_ } _%>
<%_ if (!readOnly) { _%>

Expand Down
Loading
Loading