mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
93219: Move general initialization from AppComponent to InitService
This commit is contained in:
@@ -32,7 +32,6 @@ import { storeModuleConfig } from './app.reducer';
|
|||||||
import { LocaleService } from './core/locale/locale.service';
|
import { LocaleService } from './core/locale/locale.service';
|
||||||
import { authReducer } from './core/auth/auth.reducer';
|
import { authReducer } from './core/auth/auth.reducer';
|
||||||
import { provideMockStore } from '@ngrx/store/testing';
|
import { provideMockStore } from '@ngrx/store/testing';
|
||||||
import { GoogleAnalyticsService } from './statistics/google-analytics.service';
|
|
||||||
import { ThemeService } from './shared/theme-support/theme.service';
|
import { ThemeService } from './shared/theme-support/theme.service';
|
||||||
import { getMockThemeService } from './shared/mocks/theme-service.mock';
|
import { getMockThemeService } from './shared/mocks/theme-service.mock';
|
||||||
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
||||||
@@ -46,16 +45,16 @@ const initialState = {
|
|||||||
core: { auth: { loading: false } }
|
core: { auth: { loading: false } }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function getMockLocaleService(): LocaleService {
|
||||||
|
return jasmine.createSpyObj('LocaleService', {
|
||||||
|
setCurrentLanguageCode: jasmine.createSpy('setCurrentLanguageCode')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
describe('App component', () => {
|
describe('App component', () => {
|
||||||
|
|
||||||
let breadcrumbsServiceSpy;
|
let breadcrumbsServiceSpy;
|
||||||
|
|
||||||
function getMockLocaleService(): LocaleService {
|
|
||||||
return jasmine.createSpyObj('LocaleService', {
|
|
||||||
setCurrentLanguageCode: jasmine.createSpy('setCurrentLanguageCode')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const getDefaultTestBedConf = () => {
|
const getDefaultTestBedConf = () => {
|
||||||
breadcrumbsServiceSpy = jasmine.createSpyObj(['listenForRouteChanges']);
|
breadcrumbsServiceSpy = jasmine.createSpyObj(['listenForRouteChanges']);
|
||||||
|
|
||||||
@@ -131,39 +130,6 @@ describe('App component', () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('the constructor', () => {
|
|
||||||
it('should call breadcrumbsService.listenForRouteChanges', () => {
|
|
||||||
expect(breadcrumbsServiceSpy.listenForRouteChanges).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when GoogleAnalyticsService is provided', () => {
|
|
||||||
let googleAnalyticsSpy;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
// NOTE: Cannot override providers once components have been compiled, so TestBed needs to be reset
|
|
||||||
TestBed.resetTestingModule();
|
|
||||||
TestBed.configureTestingModule(getDefaultTestBedConf());
|
|
||||||
googleAnalyticsSpy = jasmine.createSpyObj('googleAnalyticsService', [
|
|
||||||
'addTrackingIdToPage',
|
|
||||||
]);
|
|
||||||
TestBed.overrideProvider(GoogleAnalyticsService, {useValue: googleAnalyticsSpy});
|
|
||||||
fixture = TestBed.createComponent(AppComponent);
|
|
||||||
comp = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create component', () => {
|
|
||||||
expect(comp).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('the constructor', () => {
|
|
||||||
it('should call googleAnalyticsService.addTrackingIdToPage()', () => {
|
|
||||||
expect(googleAnalyticsSpy.addTrackingIdToPage).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when ThemeService returns a custom theme', () => {
|
describe('when ThemeService returns a custom theme', () => {
|
||||||
let document;
|
let document;
|
||||||
let headSpy;
|
let headSpy;
|
||||||
|
@@ -18,36 +18,24 @@ import {
|
|||||||
Router,
|
Router,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
|
|
||||||
import { isEqual } from 'lodash';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
import { BehaviorSubject, Observable, of } from 'rxjs';
|
|
||||||
import { select, Store } from '@ngrx/store';
|
import { select, Store } from '@ngrx/store';
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
|
|
||||||
|
|
||||||
import { MetadataService } from './core/metadata/metadata.service';
|
|
||||||
import { HostWindowResizeAction } from './shared/host-window.actions';
|
import { HostWindowResizeAction } from './shared/host-window.actions';
|
||||||
import { HostWindowState } from './shared/search/host-window.reducer';
|
import { HostWindowState } from './shared/search/host-window.reducer';
|
||||||
import { NativeWindowRef, NativeWindowService } from './core/services/window.service';
|
import { NativeWindowRef, NativeWindowService } from './core/services/window.service';
|
||||||
import { isAuthenticationBlocking } from './core/auth/selectors';
|
import { isAuthenticationBlocking } from './core/auth/selectors';
|
||||||
import { AuthService } from './core/auth/auth.service';
|
import { AuthService } from './core/auth/auth.service';
|
||||||
import { CSSVariableService } from './shared/sass-helper/sass-helper.service';
|
import { CSSVariableService } from './shared/sass-helper/sass-helper.service';
|
||||||
import { MenuService } from './shared/menu/menu.service';
|
import { HeadTagConfig } from '../config/theme.model';
|
||||||
import { HostWindowService } from './shared/host-window.service';
|
|
||||||
import { HeadTagConfig, ThemeConfig } from '../config/theme.model';
|
|
||||||
import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider';
|
|
||||||
import { environment } from '../environments/environment';
|
import { environment } from '../environments/environment';
|
||||||
import { models } from './core/core.module';
|
import { models } from './core/core.module';
|
||||||
import { LocaleService } from './core/locale/locale.service';
|
|
||||||
import { hasNoValue, hasValue, isNotEmpty } from './shared/empty.util';
|
import { hasNoValue, hasValue, isNotEmpty } from './shared/empty.util';
|
||||||
import { KlaroService } from './shared/cookies/klaro.service';
|
|
||||||
import { GoogleAnalyticsService } from './statistics/google-analytics.service';
|
|
||||||
import { ThemeService } from './shared/theme-support/theme.service';
|
import { ThemeService } from './shared/theme-support/theme.service';
|
||||||
import { BASE_THEME_NAME } from './shared/theme-support/theme.constants';
|
import { BASE_THEME_NAME } from './shared/theme-support/theme.constants';
|
||||||
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
|
||||||
import { IdleModalComponent } from './shared/idle-modal/idle-modal.component';
|
import { IdleModalComponent } from './shared/idle-modal/idle-modal.component';
|
||||||
import { getDefaultThemeConfig } from '../config/config.util';
|
import { getDefaultThemeConfig } from '../config/config.util';
|
||||||
import { AppConfig, APP_CONFIG } from 'src/config/app-config.interface';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-app',
|
selector: 'ds-app',
|
||||||
@@ -56,11 +44,6 @@ import { AppConfig, APP_CONFIG } from 'src/config/app-config.interface';
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit, AfterViewInit {
|
export class AppComponent implements OnInit, AfterViewInit {
|
||||||
sidebarVisible: Observable<boolean>;
|
|
||||||
slideSidebarOver: Observable<boolean>;
|
|
||||||
collapsedSidebarWidth: Observable<string>;
|
|
||||||
totalSidebarWidth: Observable<string>;
|
|
||||||
theme: Observable<ThemeConfig> = of({} as any);
|
|
||||||
notificationOptions;
|
notificationOptions;
|
||||||
models;
|
models;
|
||||||
|
|
||||||
@@ -90,29 +73,14 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
@Inject(NativeWindowService) private _window: NativeWindowRef,
|
@Inject(NativeWindowService) private _window: NativeWindowRef,
|
||||||
@Inject(DOCUMENT) private document: any,
|
@Inject(DOCUMENT) private document: any,
|
||||||
@Inject(PLATFORM_ID) private platformId: any,
|
@Inject(PLATFORM_ID) private platformId: any,
|
||||||
@Inject(APP_CONFIG) private appConfig: AppConfig,
|
|
||||||
private themeService: ThemeService,
|
private themeService: ThemeService,
|
||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
private store: Store<HostWindowState>,
|
private store: Store<HostWindowState>,
|
||||||
private metadata: MetadataService,
|
|
||||||
private angulartics2GoogleAnalytics: Angulartics2GoogleAnalytics,
|
|
||||||
private angulartics2DSpace: Angulartics2DSpace,
|
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private cssService: CSSVariableService,
|
private cssService: CSSVariableService,
|
||||||
private menuService: MenuService,
|
|
||||||
private windowService: HostWindowService,
|
|
||||||
private localeService: LocaleService,
|
|
||||||
private breadcrumbsService: BreadcrumbsService,
|
|
||||||
private modalService: NgbModal,
|
private modalService: NgbModal,
|
||||||
@Optional() private cookiesService: KlaroService,
|
|
||||||
@Optional() private googleAnalyticsService: GoogleAnalyticsService,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
if (!isEqual(environment, this.appConfig)) {
|
|
||||||
throw new Error('environment does not match app config!');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.notificationOptions = environment.notifications;
|
this.notificationOptions = environment.notifications;
|
||||||
|
|
||||||
/* Use models object so all decorators are actually called */
|
/* Use models object so all decorators are actually called */
|
||||||
@@ -136,47 +104,18 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (isPlatformBrowser(this.platformId)) {
|
if (isPlatformBrowser(this.platformId)) {
|
||||||
this.authService.trackTokenExpiration();
|
|
||||||
this.trackIdleModal();
|
this.trackIdleModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load all the languages that are defined as active from the config file
|
|
||||||
translate.addLangs(environment.languages.filter((LangConfig) => LangConfig.active === true).map((a) => a.code));
|
|
||||||
|
|
||||||
// Load the default language from the config file
|
|
||||||
// translate.setDefaultLang(environment.defaultLanguage);
|
|
||||||
|
|
||||||
// set the current language code
|
|
||||||
this.localeService.setCurrentLanguageCode();
|
|
||||||
|
|
||||||
// analytics
|
|
||||||
if (hasValue(googleAnalyticsService)) {
|
|
||||||
googleAnalyticsService.addTrackingIdToPage();
|
|
||||||
}
|
|
||||||
angulartics2DSpace.startTracking();
|
|
||||||
|
|
||||||
metadata.listenForRouteChange();
|
|
||||||
breadcrumbsService.listenForRouteChanges();
|
|
||||||
|
|
||||||
if (environment.debug) {
|
|
||||||
console.info(environment);
|
|
||||||
}
|
|
||||||
this.storeCSSVariables();
|
this.storeCSSVariables();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.isAuthBlocking$ = this.store.pipe(select(isAuthenticationBlocking)).pipe(
|
this.isAuthBlocking$ = this.store.pipe(
|
||||||
|
select(isAuthenticationBlocking),
|
||||||
distinctUntilChanged()
|
distinctUntilChanged()
|
||||||
);
|
);
|
||||||
this.isAuthBlocking$
|
|
||||||
.pipe(
|
|
||||||
filter((isBlocking: boolean) => isBlocking === false),
|
|
||||||
take(1)
|
|
||||||
).subscribe(() => this.initializeKlaro());
|
|
||||||
|
|
||||||
const env: string = environment.production ? 'Production' : 'Development';
|
|
||||||
const color: string = environment.production ? 'red' : 'green';
|
|
||||||
console.info(`Environment: %c${env}`, `color: ${color}; font-weight: bold;`);
|
|
||||||
this.dispatchWindowSize(this._window.nativeWindow.innerWidth, this._window.nativeWindow.innerHeight);
|
this.dispatchWindowSize(this._window.nativeWindow.innerWidth, this._window.nativeWindow.innerHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,12 +178,6 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeKlaro() {
|
|
||||||
if (hasValue(this.cookiesService)) {
|
|
||||||
this.cookiesService.initialize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private loadGlobalThemeConfig(themeName: string): void {
|
private loadGlobalThemeConfig(themeName: string): void {
|
||||||
this.setThemeCss(themeName);
|
this.setThemeCss(themeName);
|
||||||
this.setHeadTags(themeName);
|
this.setHeadTags(themeName);
|
||||||
|
@@ -4,7 +4,7 @@ import { HttpHeaders } from '@angular/common/http';
|
|||||||
import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens';
|
import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens';
|
||||||
|
|
||||||
import { Observable, of as observableOf } from 'rxjs';
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
import { map, startWith, switchMap, take } from 'rxjs/operators';
|
import { filter, map, startWith, switchMap, take } from 'rxjs/operators';
|
||||||
import { select, Store } from '@ngrx/store';
|
import { select, Store } from '@ngrx/store';
|
||||||
import { CookieAttributes } from 'js-cookie';
|
import { CookieAttributes } from 'js-cookie';
|
||||||
|
|
||||||
@@ -88,6 +88,8 @@ export class AuthService {
|
|||||||
private translateService: TranslateService
|
private translateService: TranslateService
|
||||||
) {
|
) {
|
||||||
this.store.pipe(
|
this.store.pipe(
|
||||||
|
// when this service is constructed the store is not fully initialized yet
|
||||||
|
filter((state: any) => state?.core?.auth !== undefined),
|
||||||
select(isAuthenticated),
|
select(isAuthenticated),
|
||||||
startWith(false)
|
startWith(false)
|
||||||
).subscribe((authenticated: boolean) => this._authenticated = authenticated);
|
).subscribe((authenticated: boolean) => this._authenticated = authenticated);
|
||||||
@@ -325,7 +327,7 @@ export class AuthService {
|
|||||||
let token: AuthTokenInfo;
|
let token: AuthTokenInfo;
|
||||||
let currentlyRefreshingToken = false;
|
let currentlyRefreshingToken = false;
|
||||||
this.store.pipe(select(getAuthenticationToken)).subscribe((authTokenInfo: AuthTokenInfo) => {
|
this.store.pipe(select(getAuthenticationToken)).subscribe((authTokenInfo: AuthTokenInfo) => {
|
||||||
// If new token is undefined an it wasn't previously => Refresh failed
|
// If new token is undefined and it wasn't previously => Refresh failed
|
||||||
if (currentlyRefreshingToken && token !== undefined && authTokenInfo === undefined) {
|
if (currentlyRefreshingToken && token !== undefined && authTokenInfo === undefined) {
|
||||||
// Token refresh failed => Error notification => 10 second wait => Page reloads & user logged out
|
// Token refresh failed => Error notification => 10 second wait => Page reloads & user logged out
|
||||||
this.notificationService.error(this.translateService.get('auth.messages.token-refresh-failed'));
|
this.notificationService.error(this.translateService.get('auth.messages.token-refresh-failed'));
|
||||||
|
@@ -1,12 +1,42 @@
|
|||||||
import { InitService } from './init.service';
|
import { InitService } from './init.service';
|
||||||
import { APP_CONFIG } from 'src/config/app-config.interface';
|
import { APP_CONFIG } from 'src/config/app-config.interface';
|
||||||
import { APP_INITIALIZER } from '@angular/core';
|
import { APP_INITIALIZER, Injectable } from '@angular/core';
|
||||||
|
import { inject, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
import { GoogleAnalyticsService } from './statistics/google-analytics.service';
|
||||||
|
import { MetadataService } from './core/metadata/metadata.service';
|
||||||
|
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Store, StoreModule } from '@ngrx/store';
|
||||||
|
import { authReducer } from './core/auth/auth.reducer';
|
||||||
|
import { storeModuleConfig } from './app.reducer';
|
||||||
|
import { AngularticsProviderMock } from './shared/mocks/angulartics-provider.service.mock';
|
||||||
|
import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider';
|
||||||
|
import { AuthService } from './core/auth/auth.service';
|
||||||
|
import { AuthServiceMock } from './shared/mocks/auth.service.mock';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { RouterMock } from './shared/mocks/router.mock';
|
||||||
|
import { MockActivatedRoute } from './shared/mocks/active-router.mock';
|
||||||
|
import { MenuService } from './shared/menu/menu.service';
|
||||||
|
import { LocaleService } from './core/locale/locale.service';
|
||||||
|
import { environment } from '../environments/environment';
|
||||||
|
import { provideMockStore } from '@ngrx/store/testing';
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
import { RouteService } from './core/services/route.service';
|
||||||
|
import { getMockLocaleService } from './app.component.spec';
|
||||||
|
import { MenuServiceStub } from './shared/testing/menu-service.stub';
|
||||||
|
import { CorrelationIdService } from './correlation-id/correlation-id.service';
|
||||||
|
import { DSpaceTransferState } from '../modules/transfer-state/dspace-transfer-state.service';
|
||||||
|
import { KlaroService } from './shared/cookies/klaro.service';
|
||||||
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { TranslateLoaderMock } from './shared/mocks/translate-loader.mock';
|
||||||
|
import { getTestScheduler } from 'jasmine-marbles';
|
||||||
import objectContaining = jasmine.objectContaining;
|
import objectContaining = jasmine.objectContaining;
|
||||||
import createSpyObj = jasmine.createSpyObj;
|
import createSpyObj = jasmine.createSpyObj;
|
||||||
import SpyObj = jasmine.SpyObj;
|
import SpyObj = jasmine.SpyObj;
|
||||||
|
|
||||||
let spy: SpyObj<any>;
|
let spy: SpyObj<any>;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
export class ConcreteInitServiceMock extends InitService {
|
export class ConcreteInitServiceMock extends InitService {
|
||||||
protected static resolveAppConfig() {
|
protected static resolveAppConfig() {
|
||||||
spy.resolveAppConfig();
|
spy.resolveAppConfig();
|
||||||
@@ -18,6 +48,15 @@ export class ConcreteInitServiceMock extends InitService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
core: {
|
||||||
|
auth: {
|
||||||
|
loading: false,
|
||||||
|
blocking: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
describe('InitService', () => {
|
describe('InitService', () => {
|
||||||
describe('providers', () => {
|
describe('providers', () => {
|
||||||
@@ -79,5 +118,156 @@ describe('InitService', () => {
|
|||||||
expect(spy.init).toHaveBeenCalled();
|
expect(spy.init).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('common initialization steps', () => {
|
||||||
|
let correlationIdServiceSpy;
|
||||||
|
let dspaceTransferStateSpy;
|
||||||
|
let transferStateSpy;
|
||||||
|
let metadataServiceSpy;
|
||||||
|
let breadcrumbsServiceSpy;
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
correlationIdServiceSpy = jasmine.createSpyObj('correlationIdServiceSpy', [
|
||||||
|
'initCorrelationId',
|
||||||
|
]);
|
||||||
|
dspaceTransferStateSpy = jasmine.createSpyObj('dspaceTransferStateSpy', [
|
||||||
|
'transfer',
|
||||||
|
]);
|
||||||
|
transferStateSpy = jasmine.createSpyObj('dspaceTransferStateSpy', [
|
||||||
|
'get', 'hasKey'
|
||||||
|
]);
|
||||||
|
breadcrumbsServiceSpy = jasmine.createSpyObj('breadcrumbsServiceSpy', [
|
||||||
|
'listenForRouteChanges',
|
||||||
|
]);
|
||||||
|
metadataServiceSpy = jasmine.createSpyObj('metadataService', [
|
||||||
|
'listenForRouteChange',
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
TestBed.resetTestingModule();
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
StoreModule.forRoot(authReducer, storeModuleConfig),
|
||||||
|
TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: TranslateLoaderMock
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: InitService, useClass: ConcreteInitServiceMock },
|
||||||
|
{ provide: CorrelationIdService, useValue: correlationIdServiceSpy },
|
||||||
|
{ provide: DSpaceTransferState, useValue: dspaceTransferStateSpy },
|
||||||
|
{ provide: APP_CONFIG, useValue: environment },
|
||||||
|
{ provide: LocaleService, useValue: getMockLocaleService() },
|
||||||
|
{ provide: Angulartics2DSpace, useValue: new AngularticsProviderMock() },
|
||||||
|
{ provide: MetadataService, useValue: metadataServiceSpy },
|
||||||
|
{ provide: BreadcrumbsService, useValue: breadcrumbsServiceSpy },
|
||||||
|
{ provide: AuthService, useValue: new AuthServiceMock() },
|
||||||
|
{ provide: Router, useValue: new RouterMock() },
|
||||||
|
{ provide: ActivatedRoute, useValue: new MockActivatedRoute() },
|
||||||
|
{ provide: MenuService, useValue: new MenuServiceStub() },
|
||||||
|
{ provide: KlaroService, useValue: undefined },
|
||||||
|
{ provide: GoogleAnalyticsService, useValue: undefined },
|
||||||
|
provideMockStore({ initialState }),
|
||||||
|
AppComponent,
|
||||||
|
RouteService,
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('initÀnalytics', () => {
|
||||||
|
describe('when GoogleAnalyticsService is provided', () => {
|
||||||
|
let googleAnalyticsSpy;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
googleAnalyticsSpy = jasmine.createSpyObj('googleAnalyticsService', [
|
||||||
|
'addTrackingIdToPage',
|
||||||
|
]);
|
||||||
|
|
||||||
|
TestBed.overrideProvider(GoogleAnalyticsService, { useValue: googleAnalyticsSpy });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call googleAnalyticsService.addTrackingIdToPage()', inject([InitService], (service) => {
|
||||||
|
// @ts-ignore
|
||||||
|
service.initAnalytics();
|
||||||
|
expect(googleAnalyticsSpy.addTrackingIdToPage).toHaveBeenCalledTimes(1);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('initRouteListeners', () => {
|
||||||
|
it('should call listenForRouteChanges', inject([InitService], (service) => {
|
||||||
|
// @ts-ignore
|
||||||
|
service.initRouteListeners();
|
||||||
|
expect(metadataServiceSpy.listenForRouteChange).toHaveBeenCalledTimes(1);
|
||||||
|
expect(breadcrumbsServiceSpy.listenForRouteChanges).toHaveBeenCalledTimes(1);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('initKlaro', () => {
|
||||||
|
const BLOCKING = {
|
||||||
|
t: { core: { auth: { blocking: true } } },
|
||||||
|
f: { core: { auth: { blocking: false } } },
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should not error out if KlaroService is not provided', inject([InitService], (service) => {
|
||||||
|
// @ts-ignore
|
||||||
|
service.initKlaro();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('when KlaroService is provided', () => {
|
||||||
|
let klaroServiceSpy;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
klaroServiceSpy = jasmine.createSpyObj('klaroServiceSpy', [
|
||||||
|
'initialize',
|
||||||
|
]);
|
||||||
|
|
||||||
|
TestBed.overrideProvider(KlaroService, { useValue: klaroServiceSpy });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not initialize Klaro while auth is blocking', () => {
|
||||||
|
getTestScheduler().run(({ cold, flush}) => {
|
||||||
|
TestBed.overrideProvider(Store, { useValue: cold('t--t--t--', BLOCKING) });
|
||||||
|
const service = TestBed.inject(InitService);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
service.initKlaro();
|
||||||
|
flush();
|
||||||
|
expect(klaroServiceSpy.initialize).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should only initialize Klaro the first time auth is unblocked', () => {
|
||||||
|
getTestScheduler().run(({ cold, flush}) => {
|
||||||
|
TestBed.overrideProvider(Store, { useValue: cold('t--t--f--t--f--', BLOCKING) });
|
||||||
|
const service = TestBed.inject(InitService);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
service.initKlaro();
|
||||||
|
flush();
|
||||||
|
expect(klaroServiceSpy.initialize).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when KlaroService is not provided', () => {
|
||||||
|
it('should not error out when auth is unblocked', () => {
|
||||||
|
getTestScheduler().run(({ cold, flush}) => {
|
||||||
|
TestBed.overrideProvider(Store, { useValue: cold('t--t--f--t--f--', BLOCKING) });
|
||||||
|
const service = TestBed.inject(InitService);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
service.initKlaro();
|
||||||
|
flush();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -5,25 +5,50 @@
|
|||||||
*
|
*
|
||||||
* http://www.dspace.org/license/
|
* http://www.dspace.org/license/
|
||||||
*/
|
*/
|
||||||
import { Store } from '@ngrx/store';
|
import { select, Store } from '@ngrx/store';
|
||||||
import { CheckAuthenticationTokenAction } from './core/auth/auth.actions';
|
import { CheckAuthenticationTokenAction } from './core/auth/auth.actions';
|
||||||
import { CorrelationIdService } from './correlation-id/correlation-id.service';
|
import { CorrelationIdService } from './correlation-id/correlation-id.service';
|
||||||
import { DSpaceTransferState } from '../modules/transfer-state/dspace-transfer-state.service';
|
import { DSpaceTransferState } from '../modules/transfer-state/dspace-transfer-state.service';
|
||||||
import { APP_INITIALIZER, Provider, Type } from '@angular/core';
|
import { APP_INITIALIZER, Inject, Optional, Provider, Type } from '@angular/core';
|
||||||
import { TransferState } from '@angular/platform-browser';
|
import { TransferState } from '@angular/platform-browser';
|
||||||
import { APP_CONFIG } from '../config/app-config.interface';
|
import { APP_CONFIG, AppConfig } from '../config/app-config.interface';
|
||||||
import { environment } from '../environments/environment';
|
import { environment } from '../environments/environment';
|
||||||
import { AppState } from './app.reducer';
|
import { AppState } from './app.reducer';
|
||||||
|
import { isEqual } from 'lodash';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { LocaleService } from './core/locale/locale.service';
|
||||||
|
import { hasValue } from './shared/empty.util';
|
||||||
|
import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider';
|
||||||
|
import { GoogleAnalyticsService } from './statistics/google-analytics.service';
|
||||||
|
import { MetadataService } from './core/metadata/metadata.service';
|
||||||
|
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
||||||
|
import { distinctUntilChanged, filter, take, tap } from 'rxjs/operators';
|
||||||
|
import { isAuthenticationBlocking } from './core/auth/selectors';
|
||||||
|
import { KlaroService } from './shared/cookies/klaro.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs the initialization of the app.
|
* Performs the initialization of the app.
|
||||||
|
*
|
||||||
* Should be extended to implement server- & browser-specific functionality.
|
* Should be extended to implement server- & browser-specific functionality.
|
||||||
|
* Initialization steps shared between the server and brower implementations
|
||||||
|
* can be included in this class.
|
||||||
|
*
|
||||||
|
* Note that the service cannot (indirectly) depend on injection tokens that are only available _after_ APP_INITIALIZER.
|
||||||
|
* For example, NgbModal depends on ApplicationRef and can therefore not be used during initialization.
|
||||||
*/
|
*/
|
||||||
export abstract class InitService {
|
export abstract class InitService {
|
||||||
protected constructor(
|
protected constructor(
|
||||||
protected store: Store<AppState>,
|
protected store: Store<AppState>,
|
||||||
protected correlationIdService: CorrelationIdService,
|
protected correlationIdService: CorrelationIdService,
|
||||||
protected dspaceTransferState: DSpaceTransferState,
|
protected dspaceTransferState: DSpaceTransferState,
|
||||||
|
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||||
|
protected translate: TranslateService,
|
||||||
|
protected localeService: LocaleService,
|
||||||
|
protected angulartics2DSpace: Angulartics2DSpace,
|
||||||
|
@Optional() protected googleAnalyticsService: GoogleAnalyticsService,
|
||||||
|
protected metadata: MetadataService,
|
||||||
|
protected breadcrumbsService: BreadcrumbsService,
|
||||||
|
@Optional() protected klaroService: KlaroService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,15 +111,108 @@ export abstract class InitService {
|
|||||||
|
|
||||||
// Common initialization steps
|
// Common initialization steps
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch a {@link CheckAuthenticationTokenAction} to start off the chain of
|
||||||
|
* actions used to determine whether a user is already logged in.
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
protected checkAuthenticationToken(): void {
|
protected checkAuthenticationToken(): void {
|
||||||
this.store.dispatch(new CheckAuthenticationTokenAction());
|
this.store.dispatch(new CheckAuthenticationTokenAction());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the correlation ID (from cookie, NgRx store or random)
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
protected initCorrelationId(): void {
|
protected initCorrelationId(): void {
|
||||||
this.correlationIdService.initCorrelationId();
|
this.correlationIdService.initCorrelationId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transfer the application's NgRx state between server-side and client-side
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
protected async transferAppState(): Promise<unknown> {
|
protected async transferAppState(): Promise<unknown> {
|
||||||
return this.dspaceTransferState.transfer();
|
return this.dspaceTransferState.transfer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure the {@link environment} matches {@link APP_CONFIG} and print
|
||||||
|
* some information about it to the console
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected checkEnvironment(): void {
|
||||||
|
if (!isEqual(environment, this.appConfig)) {
|
||||||
|
throw new Error('environment does not match app config!');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environment.debug) {
|
||||||
|
console.info(environment);
|
||||||
|
}
|
||||||
|
|
||||||
|
const env: string = environment.production ? 'Production' : 'Development';
|
||||||
|
const color: string = environment.production ? 'red' : 'green';
|
||||||
|
console.info(`Environment: %c${env}`, `color: ${color}; font-weight: bold;`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize internationalization services
|
||||||
|
* - Specify the active languages
|
||||||
|
* - Set the current locale
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected initI18n(): void {
|
||||||
|
// Load all the languages that are defined as active from the config file
|
||||||
|
this.translate.addLangs(
|
||||||
|
environment.languages
|
||||||
|
.filter((LangConfig) => LangConfig.active === true)
|
||||||
|
.map((a) => a.code)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Load the default language from the config file
|
||||||
|
// translate.setDefaultLang(environment.defaultLanguage);
|
||||||
|
|
||||||
|
this.localeService.setCurrentLanguageCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize analytics services
|
||||||
|
* - Angulartics
|
||||||
|
* - Google Analytics (if enabled)
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected initAnalytics(): void {
|
||||||
|
if (hasValue(this.googleAnalyticsService)) {
|
||||||
|
this.googleAnalyticsService.addTrackingIdToPage();
|
||||||
|
}
|
||||||
|
this.angulartics2DSpace.startTracking();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start route-listening subscriptions
|
||||||
|
* - {@link MetadataService.listenForRouteChange}
|
||||||
|
* - {@link BreadcrumbsService.listenForRouteChanges}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected initRouteListeners(): void {
|
||||||
|
this.metadata.listenForRouteChange();
|
||||||
|
this.breadcrumbsService.listenForRouteChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize Klaro (if enabled)
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected initKlaro() {
|
||||||
|
if (hasValue(this.klaroService)) {
|
||||||
|
this.store.pipe(
|
||||||
|
select(isAuthenticationBlocking),
|
||||||
|
distinctUntilChanged(),
|
||||||
|
filter((isBlocking: boolean) => isBlocking === false),
|
||||||
|
take(1)
|
||||||
|
).subscribe(() => {
|
||||||
|
this.klaroService.initialize();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,12 +10,21 @@ import { Store } from '@ngrx/store';
|
|||||||
import { AppState } from '../../app/app.reducer';
|
import { AppState } from '../../app/app.reducer';
|
||||||
import { DSpaceTransferState } from '../transfer-state/dspace-transfer-state.service';
|
import { DSpaceTransferState } from '../transfer-state/dspace-transfer-state.service';
|
||||||
import { TransferState } from '@angular/platform-browser';
|
import { TransferState } from '@angular/platform-browser';
|
||||||
import { APP_CONFIG_STATE, AppConfig } from '../../config/app-config.interface';
|
import { APP_CONFIG, APP_CONFIG_STATE, AppConfig } from '../../config/app-config.interface';
|
||||||
import { DefaultAppConfig } from '../../config/default-app-config';
|
import { DefaultAppConfig } from '../../config/default-app-config';
|
||||||
import { extendEnvironmentWithAppConfig } from '../../config/config.util';
|
import { extendEnvironmentWithAppConfig } from '../../config/config.util';
|
||||||
import { environment } from '../../environments/environment';
|
import { environment } from '../../environments/environment';
|
||||||
import { CorrelationIdService } from '../../app/correlation-id/correlation-id.service';
|
import { CorrelationIdService } from '../../app/correlation-id/correlation-id.service';
|
||||||
import { Injectable } from '@angular/core';
|
import { Inject, Injectable, Optional } from '@angular/core';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { LocaleService } from '../../app/core/locale/locale.service';
|
||||||
|
import { Angulartics2DSpace } from '../../app/statistics/angulartics/dspace-provider';
|
||||||
|
import { GoogleAnalyticsService } from '../../app/statistics/google-analytics.service';
|
||||||
|
import { MetadataService } from '../../app/core/metadata/metadata.service';
|
||||||
|
import { BreadcrumbsService } from '../../app/breadcrumbs/breadcrumbs.service';
|
||||||
|
import { CSSVariableService } from '../../app/shared/sass-helper/sass-helper.service';
|
||||||
|
import { KlaroService } from '../../app/shared/cookies/klaro.service';
|
||||||
|
import { AuthService } from '../../app/core/auth/auth.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs client-side initialization.
|
* Performs client-side initialization.
|
||||||
@@ -27,8 +36,30 @@ export class BrowserInitService extends InitService {
|
|||||||
protected correlationIdService: CorrelationIdService,
|
protected correlationIdService: CorrelationIdService,
|
||||||
protected transferState: TransferState,
|
protected transferState: TransferState,
|
||||||
protected dspaceTransferState: DSpaceTransferState,
|
protected dspaceTransferState: DSpaceTransferState,
|
||||||
|
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||||
|
protected translate: TranslateService,
|
||||||
|
protected localeService: LocaleService,
|
||||||
|
protected angulartics2DSpace: Angulartics2DSpace,
|
||||||
|
@Optional() protected googleAnalyticsService: GoogleAnalyticsService,
|
||||||
|
protected metadata: MetadataService,
|
||||||
|
protected breadcrumbsService: BreadcrumbsService,
|
||||||
|
protected cssService: CSSVariableService,
|
||||||
|
@Optional() protected klaroService: KlaroService,
|
||||||
|
protected authService: AuthService,
|
||||||
) {
|
) {
|
||||||
super(store, correlationIdService, dspaceTransferState);
|
super(
|
||||||
|
store,
|
||||||
|
correlationIdService,
|
||||||
|
dspaceTransferState,
|
||||||
|
appConfig,
|
||||||
|
translate,
|
||||||
|
localeService,
|
||||||
|
angulartics2DSpace,
|
||||||
|
googleAnalyticsService,
|
||||||
|
metadata,
|
||||||
|
breadcrumbsService,
|
||||||
|
klaroService,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static resolveAppConfig(
|
protected static resolveAppConfig(
|
||||||
@@ -47,7 +78,22 @@ export class BrowserInitService extends InitService {
|
|||||||
this.checkAuthenticationToken();
|
this.checkAuthenticationToken();
|
||||||
this.initCorrelationId();
|
this.initCorrelationId();
|
||||||
|
|
||||||
|
this.checkEnvironment();
|
||||||
|
|
||||||
|
this.initI18n();
|
||||||
|
this.initAnalytics();
|
||||||
|
this.initRouteListeners();
|
||||||
|
this.trackAuthTokenExpiration();
|
||||||
|
|
||||||
|
this.initKlaro();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Browser-only initialization steps
|
||||||
|
|
||||||
|
private trackAuthTokenExpiration(): void {
|
||||||
|
this.authService.trackTokenExpiration();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,9 +11,17 @@ import { AppState } from '../../app/app.reducer';
|
|||||||
import { TransferState } from '@angular/platform-browser';
|
import { TransferState } from '@angular/platform-browser';
|
||||||
import { DSpaceTransferState } from '../transfer-state/dspace-transfer-state.service';
|
import { DSpaceTransferState } from '../transfer-state/dspace-transfer-state.service';
|
||||||
import { CorrelationIdService } from '../../app/correlation-id/correlation-id.service';
|
import { CorrelationIdService } from '../../app/correlation-id/correlation-id.service';
|
||||||
import { APP_CONFIG_STATE, AppConfig } from '../../config/app-config.interface';
|
import { APP_CONFIG, APP_CONFIG_STATE, AppConfig } from '../../config/app-config.interface';
|
||||||
import { environment } from '../../environments/environment';
|
import { environment } from '../../environments/environment';
|
||||||
import { Injectable } from '@angular/core';
|
import { Inject, Injectable, Optional } from '@angular/core';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { LocaleService } from '../../app/core/locale/locale.service';
|
||||||
|
import { Angulartics2DSpace } from '../../app/statistics/angulartics/dspace-provider';
|
||||||
|
import { GoogleAnalyticsService } from '../../app/statistics/google-analytics.service';
|
||||||
|
import { MetadataService } from '../../app/core/metadata/metadata.service';
|
||||||
|
import { BreadcrumbsService } from '../../app/breadcrumbs/breadcrumbs.service';
|
||||||
|
import { CSSVariableService } from '../../app/shared/sass-helper/sass-helper.service';
|
||||||
|
import { KlaroService } from '../../app/shared/cookies/klaro.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs server-side initialization.
|
* Performs server-side initialization.
|
||||||
@@ -25,8 +33,29 @@ export class ServerInitService extends InitService {
|
|||||||
protected correlationIdService: CorrelationIdService,
|
protected correlationIdService: CorrelationIdService,
|
||||||
protected transferState: TransferState,
|
protected transferState: TransferState,
|
||||||
protected dspaceTransferState: DSpaceTransferState,
|
protected dspaceTransferState: DSpaceTransferState,
|
||||||
|
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||||
|
protected translate: TranslateService,
|
||||||
|
protected localeService: LocaleService,
|
||||||
|
protected angulartics2DSpace: Angulartics2DSpace,
|
||||||
|
@Optional() protected googleAnalyticsService: GoogleAnalyticsService,
|
||||||
|
protected metadata: MetadataService,
|
||||||
|
protected breadcrumbsService: BreadcrumbsService,
|
||||||
|
protected cssService: CSSVariableService,
|
||||||
|
@Optional() protected klaroService: KlaroService,
|
||||||
) {
|
) {
|
||||||
super(store, correlationIdService, dspaceTransferState);
|
super(
|
||||||
|
store,
|
||||||
|
correlationIdService,
|
||||||
|
dspaceTransferState,
|
||||||
|
appConfig,
|
||||||
|
translate,
|
||||||
|
localeService,
|
||||||
|
angulartics2DSpace,
|
||||||
|
googleAnalyticsService,
|
||||||
|
metadata,
|
||||||
|
breadcrumbsService,
|
||||||
|
klaroService,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected init(): () => Promise<boolean> {
|
protected init(): () => Promise<boolean> {
|
||||||
@@ -36,10 +65,19 @@ export class ServerInitService extends InitService {
|
|||||||
this.transferAppState(); // todo: SSR breaks if we await this (why?)
|
this.transferAppState(); // todo: SSR breaks if we await this (why?)
|
||||||
this.initCorrelationId();
|
this.initCorrelationId();
|
||||||
|
|
||||||
|
this.checkEnvironment();
|
||||||
|
this.initI18n();
|
||||||
|
this.initAnalytics();
|
||||||
|
this.initRouteListeners();
|
||||||
|
|
||||||
|
this.initKlaro();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Server-only initialization steps
|
||||||
|
|
||||||
private saveAppConfigForCSR(): void {
|
private saveAppConfigForCSR(): void {
|
||||||
this.transferState.set<AppConfig>(APP_CONFIG_STATE, environment as AppConfig);
|
this.transferState.set<AppConfig>(APP_CONFIG_STATE, environment as AppConfig);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user