[DURACOM-303] prevent possibly long-lasting search and browse calls in SSR

This commit is contained in:
FrancescoMolinaro
2024-11-21 16:28:41 +01:00
parent f097652594
commit c5fd4426cd
8 changed files with 176 additions and 16 deletions

View File

@@ -1,5 +1,5 @@
import { BrowseByDatePageComponent } from './browse-by-date-page.component';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { CommonModule } from '@angular/common';
import { RouterTestingModule } from '@angular/router/testing';
import { TranslateModule } from '@ngx-translate/core';
@@ -9,7 +9,7 @@ import { ActivatedRoute, Router } from '@angular/router';
import { BrowseService } from '../../core/browse/browse.service';
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
import { RouterMock } from '../../shared/mocks/router.mock';
import { ChangeDetectorRef, NO_ERRORS_SCHEMA } from '@angular/core';
import { ChangeDetectorRef, NO_ERRORS_SCHEMA, PLATFORM_ID } from '@angular/core';
import { of as observableOf } from 'rxjs';
import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
import { Community } from '../../core/shared/community.model';
@@ -24,6 +24,7 @@ import { APP_CONFIG } from '../../../config/app-config.interface';
import { environment } from '../../../environments/environment';
import { SortDirection } from '../../core/cache/models/sort-options.model';
import { cold } from 'jasmine-marbles';
import { Store } from "@ngrx/store";
describe('BrowseByDatePageComponent', () => {
let comp: BrowseByDatePageComponent;
@@ -95,7 +96,10 @@ describe('BrowseByDatePageComponent', () => {
{ provide: Router, useValue: new RouterMock() },
{ provide: PaginationService, useValue: paginationService },
{ provide: ChangeDetectorRef, useValue: mockCdRef },
{ provide: APP_CONFIG, useValue: environment }
{ provide: APP_CONFIG, useValue: environment },
{ provide: Store, useValue: {} },
{ provide: APP_CONFIG, useValue: environment },
{ provide: PLATFORM_ID, useValue: 'browser' },
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
@@ -131,4 +135,33 @@ describe('BrowseByDatePageComponent', () => {
//expect(comp.startsWithOptions[0]).toEqual(new Date().getUTCFullYear());
expect(comp.startsWithOptions[0]).toEqual(1960);
});
describe('when rendered in SSR', () => {
beforeEach(() => {
comp.platformId = 'server';
spyOn((comp as any).browseService, 'getBrowseEntriesFor');
});
it('should not call getBrowseEntriesFor on init', (done) => {
comp.ngOnInit();
expect((comp as any).browseService.getBrowseEntriesFor).not.toHaveBeenCalled();
comp.loading$.subscribe((res) => {
expect(res).toBeFalsy();
done();
});
});
});
describe('when rendered in CSR', () => {
beforeEach(() => {
comp.platformId = 'browser';
spyOn((comp as any).browseService, 'getBrowseEntriesFor').and.returnValue(createSuccessfulRemoteDataObject$(new BrowseEntry()));
});
it('should call getBrowseEntriesFor on init', fakeAsync(() => {
comp.ngOnInit();
tick(100);
expect((comp as any).browseService.getBrowseEntriesFor).toHaveBeenCalled();
}));
});
});

View File

@@ -1,4 +1,4 @@
import { ChangeDetectorRef, Component, Inject } from '@angular/core';
import { ChangeDetectorRef, Component, Inject, PLATFORM_ID } from '@angular/core';
import {
BrowseByMetadataPageComponent,
browseParamsToOptions
@@ -11,6 +11,7 @@ import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.serv
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
import { PaginationService } from '../../core/pagination/pagination.service';
import { map, take } from 'rxjs/operators';
import { of as observableOf } from 'rxjs';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { isValidDate } from '../../shared/date.util';
@@ -18,6 +19,7 @@ import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface';
import { RemoteData } from '../../core/data/remote-data';
import { Item } from '../../core/shared/item.model';
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
import { isPlatformServer } from "@angular/common";
@Component({
selector: 'ds-browse-by-date-page',
@@ -44,11 +46,16 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
protected cdRef: ChangeDetectorRef,
@Inject(APP_CONFIG) public appConfig: AppConfig,
public dsoNameService: DSONameService,
@Inject(PLATFORM_ID) public platformId: any,
) {
super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService);
super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService, platformId);
}
ngOnInit(): void {
if (!this.renderOnServerSide && isPlatformServer(this.platformId)) {
this.loading$ = observableOf(false);
return;
}
const sortConfig = new SortOptions('default', SortDirection.ASC);
this.startsWithType = StartsWithType.date;
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);

View File

@@ -3,7 +3,7 @@ import {
browseParamsToOptions,
getBrowseSearchOptions
} from './browse-by-metadata-page.component';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { BrowseService } from '../../core/browse/browse.service';
import { CommonModule } from '@angular/common';
import { RouterTestingModule } from '@angular/router/testing';
@@ -13,7 +13,7 @@ import { EnumKeysPipe } from '../../shared/utils/enum-keys-pipe';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
import { Observable, of as observableOf } from 'rxjs';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { NO_ERRORS_SCHEMA, PLATFORM_ID } from '@angular/core';
import { RemoteData } from '../../core/data/remote-data';
import { buildPaginatedList, PaginatedList } from '../../core/data/paginated-list.model';
import { PageInfo } from '../../core/shared/page-info.model';
@@ -111,7 +111,8 @@ describe('BrowseByMetadataPageComponent', () => {
{ provide: DSpaceObjectDataService, useValue: mockDsoService },
{ provide: PaginationService, useValue: paginationService },
{ provide: Router, useValue: new RouterMock() },
{ provide: APP_CONFIG, useValue: environmentMock }
{ provide: APP_CONFIG, useValue: environmentMock },
{ provide: PLATFORM_ID, useValue: 'browser' },
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
@@ -224,6 +225,35 @@ describe('BrowseByMetadataPageComponent', () => {
expect(result.fetchThumbnail).toBeTrue();
});
});
describe('when rendered in SSR', () => {
beforeEach(() => {
comp.platformId = 'server';
spyOn((comp as any).browseService, 'getBrowseEntriesFor');
});
it('should not call getBrowseEntriesFor on init', (done) => {
comp.ngOnInit();
expect((comp as any).browseService.getBrowseEntriesFor).not.toHaveBeenCalled();
comp.loading$.subscribe((res) => {
expect(res).toBeFalsy();
done();
});
});
});
describe('when rendered in CSR', () => {
beforeEach(() => {
comp.platformId = 'browser';
spyOn((comp as any).browseService, 'getBrowseEntriesFor').and.returnValue(createSuccessfulRemoteDataObject$(new BrowseEntry()));
});
it('should call getBrowseEntriesFor on init', fakeAsync(() => {
comp.ngOnInit();
tick(100);
expect((comp as any).browseService.getBrowseEntriesFor).toHaveBeenCalled();
}));
});
});
export function toRemoteData(objects: any[]): Observable<RemoteData<PaginatedList<any>>> {

View File

@@ -1,5 +1,5 @@
import { combineLatest as observableCombineLatest, Observable, Subscription, of as observableOf } from 'rxjs';
import { Component, Inject, OnInit, OnDestroy } from '@angular/core';
import { Component, Inject, OnInit, OnDestroy, Input, PLATFORM_ID } from '@angular/core';
import { RemoteData } from '../../core/data/remote-data';
import { PaginatedList } from '../../core/data/paginated-list.model';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
@@ -37,7 +37,10 @@ export const BBM_PAGINATION_ID = 'bbm';
* 'dc.contributor.*'
*/
export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
/**
* Defines whether to fetch search results during SSR execution
*/
@Input() renderOnServerSide = false;
/**
* The list of browse-entries to display
*/
@@ -134,6 +137,7 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
protected router: Router,
@Inject(APP_CONFIG) public appConfig: AppConfig,
public dsoNameService: DSONameService,
@Inject(PLATFORM_ID) public platformId: any,
) {
this.fetchThumbnails = this.appConfig.browseBy.showThumbnails;
@@ -146,7 +150,10 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
ngOnInit(): void {
if (!this.renderOnServerSide && isPlatformServer(this.platformId)) {
this.loading$ = observableOf(false);
return;
}
const sortConfig = new SortOptions('default', SortDirection.ASC);
this.updatePage(getBrowseSearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig));
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);

View File

@@ -1,4 +1,4 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { ActivatedRoute, Router } from '@angular/router';
import { Item } from '../../core/shared/item.model';
import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
@@ -22,6 +22,7 @@ import { PaginationService } from '../../core/pagination/pagination.service';
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
import { APP_CONFIG } from '../../../config/app-config.interface';
import { environment } from '../../../environments/environment';
import { BrowseEntry } from "../../core/shared/browse-entry.model";
describe('BrowseByTitlePageComponent', () => {
@@ -97,4 +98,33 @@ describe('BrowseByTitlePageComponent', () => {
expect(result.payload.page).toEqual(mockItems);
});
});
describe('when rendered in SSR', () => {
beforeEach(() => {
comp.platformId = 'server';
spyOn((comp as any).browseService, 'getBrowseEntriesFor');
});
it('should not call getBrowseEntriesFor on init', (done) => {
comp.ngOnInit();
expect((comp as any).browseService.getBrowseEntriesFor).not.toHaveBeenCalled();
comp.loading$.subscribe((res) => {
expect(res).toBeFalsy();
done();
});
});
});
describe('when rendered in CSR', () => {
beforeEach(() => {
comp.platformId = 'browser';
spyOn((comp as any).browseService, 'getBrowseEntriesFor').and.returnValue(createSuccessfulRemoteDataObject$(new BrowseEntry()));
});
it('should call getBrowseEntriesFor on init', fakeAsync(() => {
comp.ngOnInit();
tick(100);
expect((comp as any).browseService.getBrowseEntriesFor).toHaveBeenCalled();
}));
});
});

View File

@@ -1,7 +1,7 @@
import { HostWindowService } from '../shared/host-window.service';
import { SidebarService } from '../shared/sidebar/sidebar.service';
import { SearchComponent } from '../shared/search/search.component';
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
import { ChangeDetectionStrategy, Component, Inject, PLATFORM_ID } from '@angular/core';
import { pushInOut } from '../shared/animations/push';
import { SEARCH_CONFIG_SERVICE } from '../my-dspace-page/my-dspace-page.component';
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
@@ -35,7 +35,8 @@ export class ConfigurationSearchPageComponent extends SearchComponent {
protected routeService: RouteService,
protected router: Router,
@Inject(APP_CONFIG) protected appConfig: AppConfig,
@Inject(PLATFORM_ID) public platformId: any,
) {
super(service, sidebarService, windowService, searchConfigService, routeService, router, appConfig);
super(service, sidebarService, windowService, searchConfigService, routeService, router, appConfig, platformId);
}
}

View File

@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA, PLATFORM_ID } from '@angular/core';
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
@@ -216,6 +216,7 @@ export function configureSearchComponentTestingModule(compType, additionalDeclar
useValue: searchConfigurationServiceStub
},
{ provide: APP_CONFIG, useValue: environment },
{ provide: PLATFORM_ID, useValue: 'browser' },
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(compType, {
@@ -374,5 +375,34 @@ describe('SearchComponent', () => {
expect(result).toBeNull();
});
});
describe('when rendered in SSR', () => {
beforeEach(() => {
comp.platformId = 'server';
});
it('should not call search method on init', (done) => {
comp.ngOnInit();
//Check that the first method from which the search depend upon is not being called
expect(searchConfigurationServiceStub.getCurrentConfiguration).not.toHaveBeenCalled();
comp.initialized$.subscribe((res) => {
expect(res).toBeTruthy();
done();
});
});
});
describe('when rendered in CSR', () => {
beforeEach(() => {
comp.platformId = 'browser';
});
it('should call search method on init', fakeAsync(() => {
comp.ngOnInit();
tick(100);
//Check that the last method from which the search depend upon is being called
expect(searchServiceStub.search).toHaveBeenCalled();
}));
});
});
});

View File

@@ -1,4 +1,14 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Inject, Input, OnInit, Output, OnDestroy } from '@angular/core';
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Inject,
Input,
OnInit,
Output,
OnDestroy,
PLATFORM_ID
} from '@angular/core';
import { NavigationStart, Router } from '@angular/router';
import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs';
@@ -38,6 +48,7 @@ import { ITEM_MODULE_PATH } from '../../item-page/item-page-routing-paths';
import { COLLECTION_MODULE_PATH } from '../../collection-page/collection-page-routing-paths';
import { COMMUNITY_MODULE_PATH } from '../../community-page/community-page-routing-paths';
import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface';
import { isPlatformServer } from "@angular/common";
@Component({
selector: 'ds-search',
@@ -176,6 +187,11 @@ export class SearchComponent implements OnDestroy, OnInit {
*/
@Input() scope: string;
/**
* Defines whether to fetch search results during SSR execution
*/
@Input() renderOnServerSide = false;
/**
* The current configuration used during the search
*/
@@ -285,6 +301,7 @@ export class SearchComponent implements OnDestroy, OnInit {
protected routeService: RouteService,
protected router: Router,
@Inject(APP_CONFIG) protected appConfig: AppConfig,
@Inject(PLATFORM_ID) public platformId: any,
) {
this.isXsOrSm$ = this.windowService.isXsOrSm();
}
@@ -297,6 +314,11 @@ export class SearchComponent implements OnDestroy, OnInit {
* If something changes, update the list of scopes for the dropdown
*/
ngOnInit(): void {
if (!this.renderOnServerSide && isPlatformServer(this.platformId)) {
this.initialized$.next(true);
return;
}
if (this.useUniquePageId) {
// Create an unique pagination id related to the instance of the SearchComponent
this.paginationId = uniqueId(this.paginationId);