Merge remote-tracking branch 'origin/main' into CST-4506_item_embargo

This commit is contained in:
Giuseppe Digilio
2021-12-22 15:28:58 +01:00
153 changed files with 16482 additions and 14487 deletions

View File

@@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { FormsModule, ReactiveFormsModule, FormArray, FormControl, FormGroup,Validators, NG_VALIDATORS, NG_ASYNC_VALIDATORS } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { ActivatedRoute, Router } from '@angular/router';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
@@ -34,6 +34,7 @@ import { TranslateLoaderMock } from '../../../shared/testing/translate-loader.mo
import { RouterMock } from '../../../shared/mocks/router.mock';
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
import { Operation } from 'fast-json-patch';
import { ValidateGroupExists } from './validators/group-exists.validator';
describe('GroupFormComponent', () => {
let component: GroupFormComponent;
@@ -117,7 +118,69 @@ describe('GroupFormComponent', () => {
return null;
}
};
builderService = getMockFormBuilderService();
builderService = Object.assign(getMockFormBuilderService(),{
createFormGroup(formModel, options = null) {
const controls = {};
formModel.forEach( model => {
model.parent = parent;
const controlModel = model;
const controlState = { value: controlModel.value, disabled: controlModel.disabled };
const controlOptions = this.createAbstractControlOptions(controlModel.validators, controlModel.asyncValidators, controlModel.updateOn);
controls[model.id] = new FormControl(controlState, controlOptions);
});
return new FormGroup(controls, options);
},
createAbstractControlOptions(validatorsConfig = null, asyncValidatorsConfig = null, updateOn = null) {
return {
validators: validatorsConfig !== null ? this.getValidators(validatorsConfig) : null,
};
},
getValidators(validatorsConfig) {
return this.getValidatorFns(validatorsConfig);
},
getValidatorFns(validatorsConfig, validatorsToken = this._NG_VALIDATORS) {
let validatorFns = [];
if (this.isObject(validatorsConfig)) {
validatorFns = Object.keys(validatorsConfig).map(validatorConfigKey => {
const validatorConfigValue = validatorsConfig[validatorConfigKey];
if (this.isValidatorDescriptor(validatorConfigValue)) {
const descriptor = validatorConfigValue;
return this.getValidatorFn(descriptor.name, descriptor.args, validatorsToken);
}
return this.getValidatorFn(validatorConfigKey, validatorConfigValue, validatorsToken);
});
}
return validatorFns;
},
getValidatorFn(validatorName, validatorArgs = null, validatorsToken = this._NG_VALIDATORS) {
let validatorFn;
if (Validators.hasOwnProperty(validatorName)) { // Built-in Angular Validators
validatorFn = Validators[validatorName];
} else { // Custom Validators
if (this._DYNAMIC_VALIDATORS && this._DYNAMIC_VALIDATORS.has(validatorName)) {
validatorFn = this._DYNAMIC_VALIDATORS.get(validatorName);
} else if (validatorsToken) {
validatorFn = validatorsToken.find(validator => validator.name === validatorName);
}
}
if (validatorFn === undefined) { // throw when no validator could be resolved
throw new Error(`validator '${validatorName}' is not provided via NG_VALIDATORS, NG_ASYNC_VALIDATORS or DYNAMIC_FORM_VALIDATORS`);
}
if (validatorArgs !== null) {
return validatorFn(validatorArgs);
}
return validatorFn;
},
isValidatorDescriptor(value) {
if (this.isObject(value)) {
return value.hasOwnProperty('name') && value.hasOwnProperty('args');
}
return false;
},
isObject(value) {
return typeof value === 'object' && value !== null;
}
});
translateService = getMockTranslateService();
router = new RouterMock();
notificationService = new NotificationsServiceStub();
@@ -217,4 +280,72 @@ describe('GroupFormComponent', () => {
});
});
describe('check form validation', () => {
let groupCommunity;
beforeEach(() => {
groupName = 'testName';
groupCommunity = 'testgroupCommunity';
groupDescription = 'testgroupDescription';
expected = Object.assign(new Group(), {
name: groupName,
metadata: {
'dc.description': [
{
value: groupDescription
}
],
},
});
spyOn(component.submitForm, 'emit');
fixture.detectChanges();
component.initialisePage();
fixture.detectChanges();
});
describe('groupName, groupCommunity and groupDescription should be required', () => {
it('form should be invalid because the groupName is required', waitForAsync(() => {
fixture.whenStable().then(() => {
expect(component.formGroup.controls.groupName.valid).toBeFalse();
expect(component.formGroup.controls.groupName.errors.required).toBeTrue();
});
}));
});
describe('after inserting information groupName,groupCommunity and groupDescription not required', () => {
beforeEach(() => {
component.formGroup.controls.groupName.setValue('test');
fixture.detectChanges();
});
it('groupName should be valid because the groupName is set', waitForAsync(() => {
fixture.whenStable().then(() => {
expect(component.formGroup.controls.groupName.valid).toBeTrue();
expect(component.formGroup.controls.groupName.errors).toBeNull();
});
}));
});
describe('after already utilized groupName', () => {
beforeEach(() => {
const groupsDataServiceStubWithGroup = Object.assign(groupsDataServiceStub,{
searchGroups(query: string): Observable<RemoteData<PaginatedList<Group>>> {
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [expected]));
}
});
component.formGroup.controls.groupName.setValue('testName');
component.formGroup.controls.groupName.setAsyncValidators(ValidateGroupExists.createValidator(groupsDataServiceStubWithGroup));
fixture.detectChanges();
});
it('groupName should not be valid because groupName is already taken', waitForAsync(() => {
fixture.whenStable().then(() => {
expect(component.formGroup.controls.groupName.valid).toBeFalse();
expect(component.formGroup.controls.groupName.errors.groupExists).toBeTruthy();
});
}));
});
});
});

View File

@@ -1,4 +1,4 @@
import { Component, EventEmitter, HostListener, OnDestroy, OnInit, Output } from '@angular/core';
import { Component, EventEmitter, HostListener, OnDestroy, OnInit, Output, ChangeDetectorRef } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
@@ -16,7 +16,7 @@ import {
of as observableOf,
Subscription,
} from 'rxjs';
import { catchError, map, switchMap, take, filter } from 'rxjs/operators';
import { catchError, map, switchMap, take, filter, debounceTime } from 'rxjs/operators';
import { getCollectionEditRolesRoute } from '../../../collection-page/collection-page-routing-paths';
import { getCommunityEditRolesRoute } from '../../../community-page/community-page-routing-paths';
import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service';
@@ -45,6 +45,7 @@ import { NotificationsService } from '../../../shared/notifications/notification
import { followLink } from '../../../shared/utils/follow-link-config.model';
import { NoContent } from '../../../core/shared/NoContent.model';
import { Operation } from 'fast-json-patch';
import { ValidateGroupExists } from './validators/group-exists.validator';
@Component({
selector: 'ds-group-form',
@@ -126,6 +127,12 @@ export class GroupFormComponent implements OnInit, OnDestroy {
*/
public AlertTypeEnum = AlertType;
/**
* Subscription to email field value change
*/
groupNameValueChangeSubscribe: Subscription;
constructor(public groupDataService: GroupDataService,
private ePersonDataService: EPersonDataService,
private dSpaceObjectDataService: DSpaceObjectDataService,
@@ -136,7 +143,8 @@ export class GroupFormComponent implements OnInit, OnDestroy {
protected router: Router,
private authorizationService: AuthorizationDataService,
private modalService: NgbModal,
public requestService: RequestService) {
public requestService: RequestService,
protected changeDetectorRef: ChangeDetectorRef) {
}
ngOnInit() {
@@ -192,6 +200,14 @@ export class GroupFormComponent implements OnInit, OnDestroy {
this.groupDescription,
];
this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
if (!!this.formGroup.controls.groupName) {
this.formGroup.controls.groupName.setAsyncValidators(ValidateGroupExists.createValidator(this.groupDataService));
this.groupNameValueChangeSubscribe = this.groupName.valueChanges.pipe(debounceTime(300)).subscribe(() => {
this.changeDetectorRef.detectChanges();
});
}
this.subs.push(
observableCombineLatest(
this.groupDataService.getActiveGroup(),
@@ -201,6 +217,10 @@ export class GroupFormComponent implements OnInit, OnDestroy {
).subscribe(([activeGroup, canEdit, linkedObject]) => {
if (activeGroup != null) {
// Disable group name exists validator
this.formGroup.controls.groupName.clearAsyncValidators();
this.groupBeingEdited = activeGroup;
if (linkedObject?.name) {
@@ -436,6 +456,11 @@ export class GroupFormComponent implements OnInit, OnDestroy {
ngOnDestroy(): void {
this.groupDataService.cancelEditGroup();
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
if ( hasValue(this.groupNameValueChangeSubscribe) ) {
this.groupNameValueChangeSubscribe.unsubscribe();
}
}
/**

View File

@@ -0,0 +1,33 @@
import { AbstractControl, ValidationErrors } from '@angular/forms';
import { Observable } from 'rxjs';
import { map} from 'rxjs/operators';
import { GroupDataService } from '../../../../core/eperson/group-data.service';
import { getFirstSucceededRemoteListPayload } from '../../../../core/shared/operators';
import { Group } from '../../../../core/eperson/models/group.model';
export class ValidateGroupExists {
/**
* This method will create the validator with the groupDataService requested from component
* @param groupDataService the service with DI in the component that this validator is being utilized.
* @return Observable<ValidationErrors | null>
*/
static createValidator(groupDataService: GroupDataService) {
return (control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> => {
return groupDataService.searchGroups(control.value, {
currentPage: 1,
elementsPerPage: 100
})
.pipe(
getFirstSucceededRemoteListPayload(),
map( (groups: Group[]) => {
return groups.filter(group => group.name === control.value);
}),
map( (groups: Group[]) => {
return groups.length > 0 ? { groupExists: true } : null;
}),
);
};
}
}

View File

@@ -36,7 +36,7 @@ const ENTRY_COMPONENTS = [
export class AdminSearchModule {
/**
* NOTE: this method allows to resolve issue with components that using a custom decorator
* which are not loaded during CSR otherwise
* which are not loaded during SSR otherwise
*/
static withEntryComponents() {
return {

View File

@@ -28,7 +28,7 @@ const ENTRY_COMPONENTS = [
export class AdminWorkflowModuleModule {
/**
* NOTE: this method allows to resolve issue with components that using a custom decorator
* which are not loaded during CSR otherwise
* which are not loaded during SSR otherwise
*/
static withEntryComponents() {
return {

View File

@@ -34,7 +34,7 @@ const ENTRY_COMPONENTS = [
export class AdminModule {
/**
* NOTE: this method allows to resolve issue with components that using a custom decorator
* which are not loaded during CSR otherwise
* which are not loaded during SSR otherwise
*/
static withEntryComponents() {
return {

View File

@@ -202,8 +202,8 @@ import { GroupAdministratorGuard } from './core/data/feature-authorization/featu
{ path: '**', pathMatch: 'full', component: ThemedPageNotFoundComponent },
]}
],{
onSameUrlNavigation: 'reload',
})
onSameUrlNavigation: 'reload',
})
],
exports: [RouterModule],
})

View File

@@ -171,7 +171,8 @@ describe('App component', () => {
TestBed.configureTestingModule(getDefaultTestBedConf());
TestBed.overrideProvider(ThemeService, {useValue: getMockThemeService('custom')});
document = TestBed.inject(DOCUMENT);
headSpy = jasmine.createSpyObj('head', ['appendChild']);
headSpy = jasmine.createSpyObj('head', ['appendChild', 'getElementsByClassName']);
headSpy.getElementsByClassName.and.returnValue([]);
spyOn(document, 'getElementsByTagName').and.returnValue([headSpy]);
fixture = TestBed.createComponent(AppComponent);
comp = fixture.componentInstance;

View File

@@ -31,12 +31,12 @@ import { AuthService } from './core/auth/auth.service';
import { CSSVariableService } from './shared/sass-helper/sass-helper.service';
import { MenuService } from './shared/menu/menu.service';
import { HostWindowService } from './shared/host-window.service';
import { ThemeConfig } from '../config/theme.model';
import { HeadTagConfig, ThemeConfig } from '../config/theme.model';
import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider';
import { environment } from '../environments/environment';
import { models } from './core/core.module';
import { LocaleService } from './core/locale/locale.service';
import { 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 { DOCUMENT, isPlatformBrowser } from '@angular/common';
@@ -115,11 +115,11 @@ export class AppComponent implements OnInit, AfterViewInit {
this.isThemeCSSLoading$.next(true);
}
if (hasValue(themeName)) {
this.setThemeCss(themeName);
this.loadGlobalThemeConfig(themeName);
} else if (hasValue(DEFAULT_THEME_CONFIG)) {
this.setThemeCss(DEFAULT_THEME_CONFIG.name);
this.loadGlobalThemeConfig(DEFAULT_THEME_CONFIG.name);
} else {
this.setThemeCss(BASE_THEME_NAME);
this.loadGlobalThemeConfig(BASE_THEME_NAME);
}
});
@@ -233,6 +233,11 @@ export class AppComponent implements OnInit, AfterViewInit {
}
}
private loadGlobalThemeConfig(themeName: string): void {
this.setThemeCss(themeName);
this.setHeadTags(themeName);
}
/**
* Update the theme css file in <head>
*
@@ -241,9 +246,13 @@ export class AppComponent implements OnInit, AfterViewInit {
*/
private setThemeCss(themeName: string): void {
const head = this.document.getElementsByTagName('head')[0];
if (hasNoValue(head)) {
return;
}
// Array.from to ensure we end up with an array, not an HTMLCollection, which would be
// automatically updated if we add nodes later
const currentThemeLinks = Array.from(this.document.getElementsByClassName('theme-css'));
const currentThemeLinks = Array.from(head.getElementsByClassName('theme-css'));
const link = this.document.createElement('link');
link.setAttribute('rel', 'stylesheet');
link.setAttribute('type', 'text/css');
@@ -265,6 +274,78 @@ export class AppComponent implements OnInit, AfterViewInit {
head.appendChild(link);
}
private setHeadTags(themeName: string): void {
const head = this.document.getElementsByTagName('head')[0];
if (hasNoValue(head)) {
return;
}
// clear head tags
const currentHeadTags = Array.from(head.getElementsByClassName('theme-head-tag'));
if (hasValue(currentHeadTags)) {
currentHeadTags.forEach((currentHeadTag: any) => currentHeadTag.remove());
}
// create new head tags (not yet added to DOM)
const headTagFragment = this.document.createDocumentFragment();
this.createHeadTags(themeName)
.forEach(newHeadTag => headTagFragment.appendChild(newHeadTag));
// add new head tags to DOM
head.appendChild(headTagFragment);
}
private createHeadTags(themeName: string): HTMLElement[] {
const themeConfig = this.themeService.getThemeConfigFor(themeName);
const headTagConfigs = themeConfig?.headTags;
if (hasNoValue(headTagConfigs)) {
const parentThemeName = themeConfig?.extends;
if (hasValue(parentThemeName)) {
// inherit the head tags of the parent theme
return this.createHeadTags(parentThemeName);
}
const defaultThemeName = DEFAULT_THEME_CONFIG.name;
if (
hasNoValue(defaultThemeName) ||
themeName === defaultThemeName ||
themeName === BASE_THEME_NAME
) {
// last resort, use fallback favicon.ico
return [
this.createHeadTag({
'tagName': 'link',
'attributes': {
'rel': 'icon',
'href': 'assets/images/favicon.ico',
'sizes': 'any',
}
})
];
}
// inherit the head tags of the default theme
return this.createHeadTags(DEFAULT_THEME_CONFIG.name);
}
return headTagConfigs.map(this.createHeadTag.bind(this));
}
private createHeadTag(headTagConfig: HeadTagConfig): HTMLElement {
const tag = this.document.createElement(headTagConfig.tagName);
if (hasValue(headTagConfig.attributes)) {
Object.entries(headTagConfig.attributes)
.forEach(([key, value]) => tag.setAttribute(key, value));
}
// 'class' attribute should always be 'theme-head-tag' for removal
tag.setAttribute('class', 'theme-head-tag');
return tag;
}
private trackIdleModal() {
const isIdle$ = this.authService.isUserIdle();
const isAuthenticated$ = this.authService.isAuthenticated();

View File

@@ -10,11 +10,11 @@
</nav>
<ng-template #breadcrumb let-text="text" let-url="url">
<li class="breadcrumb-item"><a [routerLink]="url">{{text | translate}}</a></li>
<li class="breadcrumb-item"><div class="breadcrumb-item-limiter"><a [routerLink]="url" class="text-truncate">{{text | translate}}</a></div></li>
</ng-template>
<ng-template #activeBreadcrumb let-text="text">
<li class="breadcrumb-item active" aria-current="page">{{text | translate}}</li>
<li class="breadcrumb-item active" aria-current="page"><div class="breadcrumb-item-limiter"><div class="text-truncate">{{text | translate}}</div></div></li>
</ng-template>
</ng-container>

View File

@@ -10,6 +10,19 @@
background-color: var(--ds-breadcrumb-bg);
}
li.breadcrumb-item {
display: flex;
}
.breadcrumb-item-limiter {
display: inline-block;
max-width: var(--ds-breadcrumb-max-length);
> * {
max-width: 100%;
display: block;
}
}
li.breadcrumb-item > a {
color: var(--ds-breadcrumb-link-color) !important;
}
@@ -18,5 +31,6 @@ li.breadcrumb-item.active {
}
.breadcrumb-item+ .breadcrumb-item::before {
display: block;
content: quote("") !important;
}

View File

@@ -8,7 +8,7 @@ import { By } from '@angular/platform-browser';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateLoaderMock } from '../shared/testing/translate-loader.mock';
import { RouterTestingModule } from '@angular/router/testing';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { of as observableOf } from 'rxjs';
import { DebugElement } from '@angular/core';
describe('BreadcrumbsComponent', () => {
@@ -72,7 +72,7 @@ describe('BreadcrumbsComponent', () => {
expect(breadcrumbs.length).toBe(3);
expectBreadcrumb(breadcrumbs[0], 'home.breadcrumbs', '/');
expectBreadcrumb(breadcrumbs[1], 'bc 1', '/example.com');
expectBreadcrumb(breadcrumbs[2], 'bc 2', null);
expectBreadcrumb(breadcrumbs[2].query(By.css('.text-truncate')), 'bc 2', null);
});
});

View File

@@ -1,7 +1,7 @@
import { Component } from '@angular/core';
import { Breadcrumb } from './breadcrumb/breadcrumb.model';
import { BreadcrumbsService } from './breadcrumbs.service';
import { Observable } from 'rxjs/internal/Observable';
import { Observable } from 'rxjs';
/**
* Component representing the breadcrumbs of a page

View File

@@ -31,7 +31,7 @@ const ENTRY_COMPONENTS = [
export class BrowseByModule {
/**
* NOTE: this method allows to resolve issue with components that using a custom decorator
* which are not loaded during CSR otherwise
* which are not loaded during SSR otherwise
*/
static withEntryComponents() {
return {

View File

@@ -5,9 +5,10 @@
<p [innerHTML]="'collection.edit.item-mapper.collection' | translate:{ name: (collectionName$ |async) }" id="collection-name"></p>
<p>{{'collection.edit.item-mapper.description' | translate}}</p>
<ngb-tabset (tabChange)="tabChange($event)" [destroyOnHide]="true" #tabs="ngbTabset">
<ngb-tab title="{{'collection.edit.item-mapper.tabs.browse' | translate}}" id="browseTab">
<ng-template ngbTabContent>
<ul ngbNav (navChange)="tabChange($event)" [destroyOnHide]="true" #tabs="ngbNav" class="nav-tabs">
<li [ngbNavItem]="'browseTab'">
<a ngbNavLink>{{'collection.edit.item-mapper.tabs.browse' | translate}}</a>
<ng-template ngbNavContent>
<div class="mt-2">
<ds-item-select class="mt-2"
[key]="'browse'"
@@ -21,9 +22,10 @@
(cancel)="onCancel()"></ds-item-select>
</div>
</ng-template>
</ngb-tab>
<ngb-tab title="{{'collection.edit.item-mapper.tabs.map' | translate}}" id="mapTab">
<ng-template ngbTabContent>
</li>
<li [ngbNavItem]="'mapTab'">
<a ngbNavLink>{{'collection.edit.item-mapper.tabs.map' | translate}}</a>
<ng-template ngbNavContent>
<div class="row mt-2">
<div class="col-12 col-lg-6">
<ds-search-form id="search-form"
@@ -52,8 +54,9 @@
{{'collection.edit.item-mapper.no-search' | translate}}
</div>
</ng-template>
</ngb-tab>
</ngb-tabset>
</li>
</ul>
<div [ngbNavOutlet]="tabs"></div>
</div>
</div>
</div>

View File

@@ -27,7 +27,7 @@ import { ItemSelectComponent } from '../../shared/object-select/item-select/item
import { ObjectSelectService } from '../../shared/object-select/object-select.service';
import { ObjectSelectServiceStub } from '../../shared/testing/object-select-service.stub';
import { VarDirective } from '../../shared/utils/var.directive';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { of as observableOf } from 'rxjs';
import { RouteService } from '../../core/services/route.service';
import { ErrorComponent } from '../../shared/error/error.component';
import { LoadingComponent } from '../../shared/loading/loading.component';

View File

@@ -11,18 +11,16 @@ import {
import { filter, map, switchMap, tap } from 'rxjs/operators';
import { hasValue, hasValueOperator } from '../../../../shared/empty.util';
import { ProcessStatus } from '../../../../process-page/processes/process-status.model';
import { Subscription } from 'rxjs/internal/Subscription';
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { RequestService } from '../../../../core/data/request.service';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { Collection } from '../../../../core/shared/collection.model';
import { CollectionDataService } from '../../../../core/data/collection-data.service';
import { Observable } from 'rxjs/internal/Observable';
import { Process } from '../../../../process-page/processes/process.model';
import { TranslateService } from '@ngx-translate/core';
import { HttpClient } from '@angular/common/http';
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
import { ContentSourceSetSerializer } from '../../../../core/shared/content-source-set-serializer';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
/**
* Component that contains the controls to run, reset and test the harvest

View File

@@ -1,9 +1,8 @@
import { Subscription } from 'rxjs/internal/Subscription';
import { FindListOptions } from '../core/data/request.models';
import { hasValue } from '../shared/empty.util';
import { CommunityListService, FlatNode } from './community-list-service';
import { CollectionViewer, DataSource } from '@angular/cdk/collections';
import { BehaviorSubject, Observable, } from 'rxjs';
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { finalize } from 'rxjs/operators';
/**

View File

@@ -10,8 +10,7 @@ import {
LinkDefinition
} from './build-decorators';
import { RemoteData } from '../../data/remote-data';
import { Observable } from 'rxjs/internal/Observable';
import { EMPTY } from 'rxjs';
import { EMPTY, Observable } from 'rxjs';
import { ResourceType } from '../../shared/resource-type';
/**

View File

@@ -7,7 +7,7 @@ import { PostRequest } from './request.models';
import { Registration } from '../shared/registration.model';
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub';
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { of as observableOf } from 'rxjs';
import { TestScheduler } from 'rxjs/testing';
describe('EpersonRegistrationService', () => {

View File

@@ -14,7 +14,7 @@ import { FindListOptions } from './request.models';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { dataService } from '../cache/builders/build-decorators';
import { RemoteData } from './remote-data';
import { Observable } from 'rxjs/internal/Observable';
import { Observable } from 'rxjs';
import { PaginatedList } from './paginated-list.model';
import { ITEM_TYPE } from '../shared/item-relationships/item-type.resource-type';
import { LICENSE } from '../shared/license.resource-type';

View File

@@ -10,7 +10,6 @@ import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
import { difference } from '../../shared/object.util';
import { isNumeric } from 'rxjs/internal-compatibility';
@Injectable({
providedIn: 'root',
})

View File

@@ -7,7 +7,7 @@ import { HALLink } from './hal-link.model';
import { ResourceType } from './resource-type';
import { RemoteData } from '../data/remote-data';
import { PaginatedList } from '../data/paginated-list.model';
import { Observable } from 'rxjs/internal/Observable';
import { Observable } from 'rxjs';
import { ITEM_TYPE } from './item-relationships/item-type.resource-type';
import { ItemType } from './item-relationships/item-type.model';

View File

@@ -5,7 +5,7 @@ import { map, take } from 'rxjs/operators';
import { NativeWindowRef, NativeWindowService } from '../services/window.service';
import { URLCombiner } from '../url-combiner/url-combiner';
import { hasValue } from '../../shared/empty.util';
import { Observable } from 'rxjs/internal/Observable';
import { Observable } from 'rxjs';
/**
* Provides utility methods to save files on the client-side.

View File

@@ -1,4 +1,4 @@
import * as uuidv4 from 'uuid/v4';
import { v4 as uuidv4 } from 'uuid';
import { autoserialize, Serialize, Deserialize } from 'cerialize';
import { hasValue } from '../../shared/empty.util';
/* tslint:disable:max-classes-per-file */

View File

@@ -1,5 +1,5 @@
import { isUndefined } from '../../shared/empty.util';
import * as uuidv4 from 'uuid/v4';
import { v4 as uuidv4 } from 'uuid';
import { MetadataMap, MetadataValue, MetadataValueFilter, MetadatumViewModel } from './metadata.models';
import { Metadata } from './metadata.utils';

View File

@@ -26,7 +26,7 @@ import { hasNoValue, hasValue, isNotEmpty, isNotEmptyOperator } from '../../../s
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
import { SearchConfig } from './search-filters/search-config.model';
import { SearchService } from './search.service';
import { of } from 'rxjs/internal/observable/of';
import { of } from 'rxjs';
import { PaginationService } from '../../pagination/pagination.service';
/**

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import * as uuidv4 from 'uuid/v4';
import { v4 as uuidv4 } from 'uuid';
@Injectable()
export class UUIDService {

View File

@@ -8,7 +8,7 @@ import { RemoteDataBuildService } from '../cache/builders/remote-data-build.serv
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { CoreState } from '../core.reducers';
import { ClaimedTaskDataService } from './claimed-task-data.service';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { of as observableOf } from 'rxjs';
import { FindListOptions } from '../data/request.models';
import { RequestParam } from '../cache/models/request-param.model';
import { getTestScheduler } from 'jasmine-marbles';

View File

@@ -10,7 +10,7 @@ import { CoreState } from '../core.reducers';
import { PoolTaskDataService } from './pool-task-data.service';
import { getTestScheduler } from 'jasmine-marbles';
import { TestScheduler } from 'rxjs/testing';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { of as observableOf } from 'rxjs';
import { FindListOptions } from '../data/request.models';
import { RequestParam } from '../cache/models/request-param.model';
import { HttpOptions } from '../dspace-rest/dspace-rest.service';

View File

@@ -17,7 +17,7 @@ import { HttpClient, HttpHeaders } from '@angular/common/http';
import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
import { ChangeAnalyzer } from '../data/change-analyzer';
import { compare, Operation } from 'fast-json-patch';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { of as observableOf } from 'rxjs';
import { HttpOptions } from '../dspace-rest/dspace-rest.service';
import { getMockRemoteDataBuildService } from '../../shared/mocks/remote-data-build.service.mock';
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';

View File

@@ -8,11 +8,10 @@ import {
HttpResponse,
HttpXsrfTokenExtractor
} from '@angular/common/http';
import { Observable } from 'rxjs/internal/Observable';
import { Observable, throwError } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
import { CookieService } from '../services/cookie.service';
import { throwError } from 'rxjs';
// Name of XSRF header we may send in requests to backend (this is a standard name defined by Angular)
export const XSRF_REQUEST_HEADER = 'X-XSRF-TOKEN';

View File

@@ -1,10 +1,10 @@
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<ds-truncatable [id]="dso.id">
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
[routerLink]="[itemPageRoute]" class="lead item-list-title"
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out"
[innerHTML]="dsoTitle"></a>
<span *ngIf="linkType == linkTypes.None"
class="lead item-list-title"
class="lead item-list-title dont-break-out"
[innerHTML]="dsoTitle"></span>
<span class="text-muted">
<ds-truncatable-part [id]="dso.id" [minLines]="1">

View File

@@ -1,10 +1,10 @@
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<ds-truncatable [id]="dso.id">
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
[routerLink]="[itemPageRoute]" class="lead item-list-title"
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out"
[innerHTML]="dsoTitle"></a>
<span *ngIf="linkType == linkTypes.None"
class="lead item-list-title"
class="lead item-list-title dont-break-out"
[innerHTML]="dsoTitle"></span>
<span class="text-muted">
<ds-truncatable-part [id]="dso.id" [minLines]="1">

View File

@@ -1,10 +1,10 @@
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<ds-truncatable [id]="dso.id">
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
[routerLink]="[itemPageRoute]" class="lead item-list-title"
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out"
[innerHTML]="dsoTitle"></a>
<span *ngIf="linkType == linkTypes.None"
class="lead item-list-title"
class="lead item-list-title dont-break-out"
[innerHTML]="dsoTitle"></span>
<span class="text-muted">
<ds-truncatable-part [id]="dso.id" [minLines]="1">

View File

@@ -54,7 +54,7 @@ const ENTRY_COMPONENTS = [
export class JournalEntitiesModule {
/**
* NOTE: this method allows to resolve issue with components that using a custom decorator
* which are not loaded during CSR otherwise
* which are not loaded during SSR otherwise
*/
static withEntryComponents() {
return {

View File

@@ -1,10 +1,10 @@
<ds-truncatable [id]="dso.id">
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
[routerLink]="[itemPageRoute]" class="lead item-list-title"
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out"
[innerHTML]="dsoTitle"></a>
<span *ngIf="linkType == linkTypes.None"
class="lead item-list-title"
class="lead item-list-title dont-break-out"
[innerHTML]="dsoTitle"></span>
<!--<span class="text-muted">-->
<!--<ds-truncatable-part [id]="dso.id" [minLines]="1">-->

View File

@@ -74,7 +74,7 @@ const COMPONENTS = [
export class ResearchEntitiesModule {
/**
* NOTE: this method allows to resolve issue with components that using a custom decorator
* which are not loaded during CSR otherwise
* which are not loaded during SSR otherwise
*/
static withEntryComponents() {
return {

View File

@@ -1,18 +1,24 @@
<ng-template #bitstreamView>
<div class="{{columnSizes.columns[0].buildClasses()}} row-element d-flex">
<ng-content select="[slot=drag-handle]"></ng-content>
<div class="float-left d-flex align-items-center">
{{ bitstreamName }}
<div class="float-left d-flex align-items-center overflow-hidden">
<span class="text-truncate">
{{ bitstreamName }}
</span>
</div>
</div>
<div class="{{columnSizes.columns[1].buildClasses()}} row-element d-flex align-items-center">
<div class="w-100">
<span class="text-truncate">
{{ bitstream?.firstMetadataValue('dc.description') }}
</span>
</div>
</div>
<div class="{{columnSizes.columns[2].buildClasses()}} row-element d-flex align-items-center">
<div class="text-center w-100">
{{ (format$ | async)?.shortDescription }}
<span class="text-truncate">
{{ (format$ | async)?.shortDescription }}
</span>
</div>
</div>
<div class="{{columnSizes.columns[3].buildClasses()}} row-element d-flex align-items-center">

View File

@@ -5,9 +5,10 @@
<p [innerHTML]="'item.edit.item-mapper.item' | translate:{ name: (itemName$ | async) }" id="item-name"></p>
<p>{{'item.edit.item-mapper.description' | translate}}</p>
<ngb-tabset (tabChange)="tabChange($event)" [destroyOnHide]="true" #tabs="ngbTabset">
<ngb-tab title="{{'item.edit.item-mapper.tabs.browse' | translate}}" id="browseTab">
<ng-template ngbTabContent>
<ul ngbNav (navChange)="tabChange($event)" [destroyOnHide]="true" #tabs="ngbNav" class="nav-tabs">
<li [ngbNavItem]="'browseTab'">
<a ngbNavLink>{{'item.edit.item-mapper.tabs.browse' | translate}}</a>
<ng-template ngbNavContent>
<div class="mt-2">
<ds-collection-select class="mt-2"
[key]="'browse'"
@@ -20,9 +21,10 @@
(cancel)="onCancel()"></ds-collection-select>
</div>
</ng-template>
</ngb-tab>
<ngb-tab title="{{'item.edit.item-mapper.tabs.map' | translate}}" id="mapTab">
<ng-template ngbTabContent>
</li>
<li [ngbNavItem]="'mapTab'">
<a ngbNavLink>{{'item.edit.item-mapper.tabs.map' | translate}}</a>
<ng-template ngbNavContent>
<div class="row mt-2">
<div class="col-12 col-lg-6">
<ds-search-form id="search-form"
@@ -50,8 +52,9 @@
{{'item.edit.item-mapper.no-search' | translate}}
</div>
</ng-template>
</ngb-tab>
</ngb-tabset>
</li>
</ul>
<div [ngbNavOutlet]="tabs"></div>
</div>
</div>
</div>

View File

@@ -7,7 +7,7 @@ import { ActivatedRoute, Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { of as observableOf } from 'rxjs';
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
import { CollectionDataService } from '../../../core/data/collection-data.service';
import { ItemDataService } from '../../../core/data/item-data.service';

View File

@@ -26,7 +26,7 @@
<td class="w-100">
<div class="value-field">
<div *ngIf="!(editable | async)">
<span>{{metadata?.value}}</span>
<span class="dont-break-out">{{metadata?.value}}</span>
</div>
<div *ngIf="(editable | async)" class="field-container">
<textarea class="form-control" type="textarea" attr.aria-labelledby="fieldValue" [(ngModel)]="metadata.value" [dsDebounce]

View File

@@ -3,7 +3,7 @@ import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { LinkService } from '../../../../core/cache/builders/link.service';
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
import { combineLatest as observableCombineLatest, from as observableFrom, Observable } from 'rxjs';
import { combineLatest as observableCombineLatest, from as observableFrom, BehaviorSubject, Observable, Subscription } from 'rxjs';
import {
FieldUpdate,
FieldUpdates,
@@ -30,8 +30,6 @@ import { followLink } from '../../../../shared/utils/follow-link-config.model';
import { PaginatedList } from '../../../../core/data/paginated-list.model';
import { RemoteData } from '../../../../core/data/remote-data';
import { Collection } from '../../../../core/shared/collection.model';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { Subscription } from 'rxjs/internal/Subscription';
import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model';
import { PaginationService } from '../../../../core/pagination/pagination.service';
import { RelationshipTypeService } from '../../../../core/data/relationship-type.service';

View File

@@ -6,9 +6,8 @@ import {
FieldUpdates,
RelationshipIdentifiable,
} from '../../../core/data/object-updates/object-updates.reducer';
import { Observable } from 'rxjs/internal/Observable';
import { map, startWith, switchMap, take } from 'rxjs/operators';
import { combineLatest as observableCombineLatest, of as observableOf, zip as observableZip } from 'rxjs';
import { combineLatest as observableCombineLatest, of as observableOf, zip as observableZip, Observable } from 'rxjs';
import { followLink } from '../../../shared/utils/follow-link-config.model';
import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component';
import { ItemDataService } from '../../../core/data/item-data.service';

View File

@@ -1,5 +1,5 @@
<ds-metadata-field-wrapper [label]="label | translate">
<a *ngFor="let mdValue of mdValues; let last=last;" [href]="mdValue.value">
<a class="dont-break-out" *ngFor="let mdValue of mdValues; let last=last;" [href]="mdValue.value">
{{ linktext || mdValue.value }}<span *ngIf="!last" [innerHTML]="separator"></span>
</a>
</ds-metadata-field-wrapper>

View File

@@ -1,5 +1,5 @@
<ds-metadata-field-wrapper [label]="label | translate">
<span *ngFor="let mdValue of mdValues; let last=last;">
<span class="dont-break-out" *ngFor="let mdValue of mdValues; let last=last;">
{{mdValue.value}}<span *ngIf="!last" [innerHTML]="separator"></span>
</span>
</ds-metadata-field-wrapper>

View File

@@ -33,7 +33,7 @@ import { NgxGalleryModule } from '@kolkov/ngx-gallery';
import { MiradorViewerComponent } from './mirador-viewer/mirador-viewer.component';
import { VersionPageComponent } from './version-page/version-page/version-page.component';
import { VersionedItemComponent } from './simple/item-types/versioned-item/versioned-item.component';
import { ThemedFileSectionComponent} from './simple/field-components/file-section/themed-file-section.component';
import { ThemedFileSectionComponent } from './simple/field-components/file-section/themed-file-section.component';
const ENTRY_COMPONENTS = [
@@ -91,7 +91,7 @@ const DECLARATIONS = [
export class ItemPageModule {
/**
* NOTE: this method allows to resolve issue with components that using a custom decorator
* which are not loaded during CSR otherwise
* which are not loaded during SSR otherwise
*/
static withEntryComponents() {
return {

View File

@@ -1,5 +1,5 @@
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { NgxGalleryOptions } from '@kolkov/ngx-gallery';
import { Bitstream } from '../../../core/shared/bitstream.model';
import { MediaViewerItem } from '../../../core/shared/media-viewer-item.model';
@@ -55,7 +55,7 @@ describe('MediaViewerImageComponent', () => {
]
);
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports:[],
declarations: [MediaViewerImageComponent],

View File

@@ -1,5 +1,5 @@
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs';
@@ -17,7 +17,7 @@ describe('MediaViewerVideoComponent', () => {
let component: MediaViewerVideoComponent;
let fixture: ComponentFixture<MediaViewerVideoComponent>;
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot({

View File

@@ -1,4 +1,4 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { Bitstream } from '../../core/shared/bitstream.model';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
import { createPaginatedList } from '../../shared/testing/utils.test';
@@ -60,7 +60,7 @@ describe('MediaViewerComponent', () => {
{ bitstream: mockBitstream, format: 'image', thumbnail: null }
);
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot({

View File

@@ -3,9 +3,8 @@ import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { Item } from '../../core/shared/item.model';
import { environment } from '../../../environments/environment';
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
import { Observable } from 'rxjs/internal/Observable';
import { Observable, of } from 'rxjs';
import { map, take } from 'rxjs/operators';
import { of } from 'rxjs';
import { isPlatformBrowser } from '@angular/common';
import { MiradorViewerService } from './mirador-viewer.service';
import { HostWindowService, WidthCategory } from '../../shared/host-window.service';

View File

@@ -1,5 +1,5 @@
import { Injectable, isDevMode } from '@angular/core';
import { Observable } from 'rxjs/internal/Observable';
import { Observable } from 'rxjs';
import { Item } from '../../core/shared/item.model';
import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../core/shared/operators';
import { last, map, switchMap } from 'rxjs/operators';

View File

@@ -4,7 +4,7 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { Store } from '@ngrx/store';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { Observable } from 'rxjs/internal/Observable';
import { Observable } from 'rxjs';
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
import { ObjectCacheService } from '../../../../core/cache/object-cache.service';
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';

View File

@@ -1,17 +1,21 @@
<ngb-tabset *ngIf="relationTypes.length > 1" [destroyOnHide]="true" #tabs="ngbTabset" [activeId]="activeTab$ | async" (tabChange)="onTabChange($event)">
<ngb-tab *ngFor="let relationType of relationTypes" title="{{'item.page.relationships.' + relationType.label | translate}}" [id]="relationType.filter">
<ng-template ngbTabContent>
<div class="mt-4">
<ds-related-entities-search [item]="item"
[relationType]="relationType.filter"
[configuration]="relationType.configuration"
[searchEnabled]="searchEnabled"
[sideBarWidth]="sideBarWidth">
</ds-related-entities-search>
</div>
</ng-template>
</ngb-tab>
</ngb-tabset>
<ng-container *ngIf="relationTypes.length > 1">
<ul ngbNav #tabs="ngbNav" [destroyOnHide]="true" [activeId]="activeTab$ | async" (navChange)="onTabChange($event)" class="nav-tabs">
<li *ngFor="let relationType of relationTypes" [ngbNavItem]="relationType.filter">
<a ngbNavLink>{{'item.page.relationships.' + relationType.label | translate}}</a>
<ng-template ngbNavContent>
<div class="mt-4">
<ds-related-entities-search [item]="item"
[relationType]="relationType.filter"
[configuration]="relationType.configuration"
[searchEnabled]="searchEnabled"
[sideBarWidth]="sideBarWidth">
</ds-related-entities-search>
</div>
</ng-template>
</li>
</ul>
<div [ngbNavOutlet]="tabs"></div>
</ng-container>
<div *ngIf="relationTypes.length === 1" class="mt-4">
<ds-related-entities-search *ngVar="relationTypes[0] as relationType" [item]="item"
[relationType]="relationType.filter"

View File

@@ -1,5 +1,5 @@
import { Component, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing';
import { Router } from '@angular/router';
import { By } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
@@ -75,7 +75,7 @@ describe('MyDSpaceNewExternalDropdownComponent test', () => {
};
describe('With only one Entity', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
CommonModule,
@@ -126,7 +126,7 @@ describe('MyDSpaceNewExternalDropdownComponent test', () => {
});
describe('With more than one Entity', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
CommonModule,

View File

@@ -1,5 +1,5 @@
import { Component, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
@@ -79,7 +79,7 @@ describe('MyDSpaceNewSubmissionDropdownComponent test', () => {
};
describe('With only one Entity', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
CommonModule,
@@ -130,7 +130,7 @@ describe('MyDSpaceNewSubmissionDropdownComponent test', () => {
});
describe('With more than one Entity', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
CommonModule,

View File

@@ -4,7 +4,7 @@
<ds-search-sidebar *ngIf="!(isXsOrSm$ | async)" class="col-3 sidebar-md-sticky"
id="search-sidebar"
[configurationList]="(configurationList$ | async)"
[resultCount]="(resultsRD$ | async)?.payload.totalElements"
[resultCount]="(resultsRD$ | async)?.payload?.totalElements"
[viewModeList]="viewModeList"
[searchOptions]="(searchOptions$ | async)"
[sortOptions]="(sortOptions$ | async)"
@@ -27,7 +27,7 @@
<ds-search-sidebar *ngIf="(isXsOrSm$ | async)" class="col-12"
id="search-sidebar-sm"
[configurationList]="(configurationList$ | async)"
[resultCount]="(resultsRD$ | async)?.payload.totalElements"
[resultCount]="(resultsRD$ | async)?.payload?.totalElements"
(toggleSidebar)="closeSidebar()"
[ngClass]="{'active': !(isSidebarCollapsed() | async)}"
[searchOptions]="(searchOptions$ | async)"

View File

@@ -19,7 +19,7 @@ import { PaginatedSearchOptions } from '../shared/search/paginated-search-option
import { SearchService } from '../core/shared/search/search.service';
import { SidebarService } from '../shared/sidebar/sidebar.service';
import { hasValue } from '../shared/empty.util';
import { getFirstSucceededRemoteData } from '../core/shared/operators';
import { getFirstCompletedRemoteData } from '../core/shared/operators';
import { MyDSpaceResponseParsingService } from '../core/data/mydspace-response-parsing.service';
import { SearchConfigurationOption } from '../shared/search/search-switch-configuration/search-configuration-option.model';
import { RoleType } from '../core/roles/role-types';
@@ -30,7 +30,7 @@ import { MyDSpaceRequest } from '../core/data/request.models';
import { SearchResult } from '../shared/search/search-result.model';
import { Context } from '../core/shared/context.model';
import { SortOptions } from '../core/cache/models/sort-options.model';
import { RouteService } from '../core/services/route.service';
import { SearchObjects } from '../shared/search/search-objects.model';
export const MYDSPACE_ROUTE = '/mydspace';
export const SEARCH_CONFIG_SERVICE: InjectionToken<SearchConfigurationService> = new InjectionToken<SearchConfigurationService>('searchConfigurationService');
@@ -111,8 +111,7 @@ export class MyDSpacePageComponent implements OnInit {
constructor(private service: SearchService,
private sidebarService: SidebarService,
private windowService: HostWindowService,
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: MyDSpaceConfigurationService,
private routeService: RouteService) {
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: MyDSpaceConfigurationService) {
this.isXsOrSm$ = this.windowService.isXsOrSm();
this.service.setServiceOptions(MyDSpaceResponseParsingService, MyDSpaceRequest);
}
@@ -134,8 +133,8 @@ export class MyDSpacePageComponent implements OnInit {
this.searchOptions$ = this.searchConfigService.paginatedSearchOptions;
this.sub = this.searchOptions$.pipe(
tap(() => this.resultsRD$.next(null)),
switchMap((options: PaginatedSearchOptions) => this.service.search(options).pipe(getFirstSucceededRemoteData())))
.subscribe((results) => {
switchMap((options: PaginatedSearchOptions) => this.service.search(options).pipe(getFirstCompletedRemoteData())))
.subscribe((results: RemoteData<SearchObjects<DSpaceObject>>) => {
this.resultsRD$.next(results);
});

View File

@@ -10,5 +10,5 @@
</ds-viewable-collection>
</div>
<ds-loading *ngIf="isLoading()" message="{{'loading.mydspace-results' | translate}}"></ds-loading>
<ds-error *ngIf="searchResults?.hasFailed && (!searchResults?.errorMessage || searchResults?.statusCode != 400)" message="{{'error.search-results' | translate}}"></ds-error>
<ds-error *ngIf="showError()" message="{{errorMessageLabel() | translate}}"></ds-error>
<h3 *ngIf="searchResults?.payload?.page.length == 0" class="text-center text-muted" ><span>{{'mydspace.results.no-results' | translate}}</span></h3>

View File

@@ -40,9 +40,19 @@ describe('MyDSpaceResultsComponent', () => {
expect(fixture.debugElement.query(By.css('a'))).toBeNull();
});
it('should display error message if error is != 400', () => {
(comp as any).searchResults = { hasFailed: true, error: { statusCode: 500 } };
it('should display error message if error is 500', () => {
(comp as any).searchResults = { hasFailed: true, statusCode: 500 };
fixture.detectChanges();
expect(comp.showError()).toBeTrue();
expect(comp.errorMessageLabel()).toBe('error.search-results');
expect(fixture.debugElement.query(By.css('ds-error'))).not.toBeNull();
});
it('should display error message if error is 422', () => {
(comp as any).searchResults = { hasFailed: true, statusCode: 422 };
fixture.detectChanges();
expect(comp.showError()).toBeTrue();
expect(comp.errorMessageLabel()).toBe('error.invalid-search-query');
expect(fixture.debugElement.query(By.css('ds-error'))).not.toBeNull();
});

View File

@@ -58,4 +58,12 @@ export class MyDSpaceResultsComponent {
isLoading() {
return !this.searchResults || isEmpty(this.searchResults) || this.searchResults.isLoading;
}
showError(): boolean {
return this.searchResults?.hasFailed && (!this.searchResults?.errorMessage || this.searchResults?.statusCode !== 400);
}
errorMessageLabel(): string {
return (this.searchResults?.statusCode === 422) ? 'error.invalid-search-query' : 'error.search-results';
}
}

View File

@@ -50,7 +50,7 @@ const ENTRY_COMPONENTS = [
export class MyDspaceSearchModule {
/**
* NOTE: this method allows to resolve issue with components that using a custom decorator
* which are not loaded during CSR otherwise
* which are not loaded during SSR otherwise
*/
static withEntryComponents() {
return {

View File

@@ -58,7 +58,7 @@ const ENTRY_COMPONENTS = [
export class NavbarModule {
/**
* NOTE: this method allows to resolve issue with components that using a custom decorator
* which are not loaded during CSR otherwise
* which are not loaded during SSR otherwise
*/
static withEntryComponents() {
return {

View File

@@ -2,7 +2,7 @@
<div class="d-flex">
<h2 class="flex-grow-1">{{'process.detail.title' | translate:{id: process?.processId, name: process?.scriptName} }}</h2>
<div>
<a class="btn btn-light" [routerLink]="'/processes/new'" [queryParams]="{id: process?.processId}">{{'process.detail.create' | translate}}</a>
<button class="btn btn-lg btn-success " routerLink="/processes/new" [queryParams]="{id: process?.processId}"><i class="fas fa-plus pr-2"></i>{{'process.detail.create' | translate}}</button>
</div>
</div>
<ds-process-detail-field id="process-name" [title]="'process.detail.script'">
@@ -23,11 +23,11 @@
</div>
<ds-process-detail-field *ngIf="process && process.startTime" id="process-start-time" [title]="'process.detail.start-time' | translate">
<div>{{ process.startTime }}</div>
<div>{{ process.startTime | date:dateFormat:'UTC' }}</div>
</ds-process-detail-field>
<ds-process-detail-field *ngIf="process && process.endTime" id="process-end-time" [title]="'process.detail.end-time' | translate">
<div>{{ process.endTime }}</div>
<div>{{ process.endTime | date:dateFormat:'UTC' }}</div>
</ds-process-detail-field>
<ds-process-detail-field *ngIf="process && process.processStatus" id="process-status" [title]="'process.detail.status' | translate">
@@ -35,7 +35,7 @@
</ds-process-detail-field>
<ds-process-detail-field *ngIf="isProcessFinished(process)" id="process-output" [title]="'process.detail.output'">
<button *ngIf="!showOutputLogs && process?._links?.output?.href != undefined" id="showOutputButton" class="btn btn-light" (click)="showProcessOutputLogs()">
<button *ngIf="!showOutputLogs && process?._links?.output?.href != undefined" id="showOutputButton" class="btn btn-primary" (click)="showProcessOutputLogs()">
{{ 'process.detail.logs.button' | translate }}
</button>
<ds-loading *ngIf="retrievingOutputLogs$ | async" class="ds-loading" message="{{ 'process.detail.logs.loading' | translate }}"></ds-loading>
@@ -47,7 +47,7 @@
</p>
</ds-process-detail-field>
<div>
<a class="btn btn-light mt-3" [routerLink]="'/processes'">{{'process.detail.back' | translate}}</a>
<div style="text-align: right;">
<a class="btn btn-outline-secondary mt-3" [routerLink]="'/processes'">{{'process.detail.back' | translate}}</a>
</div>
</div>

View File

@@ -66,6 +66,11 @@ export class ProcessDetailComponent implements OnInit {
*/
retrievingOutputLogs$: BehaviorSubject<boolean>;
/**
* Date format to use for start and end time of processes
*/
dateFormat = 'yyyy-MM-dd HH:mm:ss ZZZZ';
constructor(protected route: ActivatedRoute,
protected router: Router,
protected processService: ProcessDataService,

View File

@@ -26,8 +26,8 @@
<td><a [routerLink]="['/processes/', process.processId]">{{process.processId}}</a></td>
<td><a [routerLink]="['/processes/', process.processId]">{{process.scriptName}}</a></td>
<td *ngVar="(getEpersonName(process.userId) | async) as ePersonName">{{ePersonName}}</td>
<td>{{process.startTime | date:dateFormat}}</td>
<td>{{process.endTime | date:dateFormat}}</td>
<td>{{process.startTime | date:dateFormat:'UTC'}}</td>
<td>{{process.endTime | date:dateFormat:'UTC'}}</td>
<td>{{process.processStatus}}</td>
</tr>
</tbody>

View File

@@ -12,12 +12,9 @@ import { By } from '@angular/platform-browser';
import { ProcessStatus } from '../processes/process-status.model';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
import { createPaginatedList } from '../../shared/testing/utils.test';
import { of as observableOf } from 'rxjs';
import { PaginationService } from '../../core/pagination/pagination.service';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { FindListOptions } from '../../core/data/request.models';
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
import { DatePipe } from '@angular/common';
describe('ProcessOverviewComponent', () => {
let component: ProcessOverviewComponent;
@@ -30,27 +27,29 @@ describe('ProcessOverviewComponent', () => {
let processes: Process[];
let ePerson: EPerson;
const pipe = new DatePipe('en-US');
function init() {
processes = [
Object.assign(new Process(), {
processId: 1,
scriptName: 'script-name',
startTime: '2020-03-19',
endTime: '2020-03-19',
startTime: '2020-03-19 00:30:00',
endTime: '2020-03-19 23:30:00',
processStatus: ProcessStatus.COMPLETED
}),
Object.assign(new Process(), {
processId: 2,
scriptName: 'script-name',
startTime: '2020-03-20',
endTime: '2020-03-20',
startTime: '2020-03-20 00:30:00',
endTime: '2020-03-20 23:30:00',
processStatus: ProcessStatus.FAILED
}),
Object.assign(new Process(), {
processId: 3,
scriptName: 'another-script-name',
startTime: '2020-03-21',
endTime: '2020-03-21',
startTime: '2020-03-21 00:30:00',
endTime: '2020-03-21 23:30:00',
processStatus: ProcessStatus.RUNNING
})
];
@@ -135,14 +134,14 @@ describe('ProcessOverviewComponent', () => {
it('should display the start time in the fourth column', () => {
rowElements.forEach((rowElement, index) => {
const el = rowElement.query(By.css('td:nth-child(4)')).nativeElement;
expect(el.textContent).toContain(processes[index].startTime);
expect(el.textContent).toContain(pipe.transform(processes[index].startTime, component.dateFormat, 'UTC'));
});
});
it('should display the end time in the fifth column', () => {
rowElements.forEach((rowElement, index) => {
const el = rowElement.query(By.css('td:nth-child(5)')).nativeElement;
expect(el.textContent).toContain(processes[index].endTime);
expect(el.textContent).toContain(pipe.transform(processes[index].endTime, component.dateFormat, 'UTC'));
});
});

View File

@@ -23,7 +23,7 @@ export class ProcessPageResolver implements Resolve<RemoteData<Process>> {
* or an error if something went wrong
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Process>> {
return this.processService.findById(route.params.id, true, false, followLink('script')).pipe(
return this.processService.findById(route.params.id, false, true, followLink('script')).pipe(
getFirstCompletedRemoteData(),
);
}

View File

@@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { map, switchMap } from 'rxjs/operators';
import { ItemRequest } from '../../core/shared/item-request.model';
import { Observable } from 'rxjs/internal/Observable';
import { Observable } from 'rxjs';
import {
getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload,
redirectOn4xx

View File

@@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { map, switchMap } from 'rxjs/operators';
import { ItemRequest } from '../../core/shared/item-request.model';
import { Observable } from 'rxjs/internal/Observable';
import { Observable } from 'rxjs';
import {
getFirstCompletedRemoteData,
getFirstSucceededRemoteDataPayload,

View File

@@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { map, switchMap } from 'rxjs/operators';
import { ItemRequest } from '../../core/shared/item-request.model';
import { Observable } from 'rxjs/internal/Observable';
import { Observable } from 'rxjs';
import {
getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload,
redirectOn4xx

View File

@@ -1,7 +1,7 @@
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { RemoteData } from '../core/data/remote-data';
import { ItemRequest } from '../core/shared/item-request.model';
import { Observable } from 'rxjs/internal/Observable';
import { Observable } from 'rxjs';
import { ItemRequestDataService } from '../core/data/item-request-data.service';
import { Injectable } from '@angular/core';
import { getFirstCompletedRemoteData } from '../core/shared/operators';

View File

@@ -8,7 +8,7 @@ import { pushInOut } from '../shared/animations/push';
import { HostWindowService } from '../shared/host-window.service';
import { SidebarService } from '../shared/sidebar/sidebar.service';
import { hasValue, isEmpty } from '../shared/empty.util';
import { getFirstSucceededRemoteData } from '../core/shared/operators';
import { getFirstCompletedRemoteData } from '../core/shared/operators';
import { RouteService } from '../core/services/route.service';
import { SEARCH_CONFIG_SERVICE } from '../my-dspace-page/my-dspace-page.component';
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
@@ -126,12 +126,12 @@ export class SearchComponent implements OnInit {
this.searchOptions$ = this.getSearchOptions();
this.sub = this.searchOptions$.pipe(
switchMap((options) => this.service.search(
options, undefined, true, true, followLink<Item>('thumbnail', { isOptional: true })
).pipe(getFirstSucceededRemoteData(), startWith(undefined))
options, undefined, false, true, followLink<Item>('thumbnail', { isOptional: true })
).pipe(getFirstCompletedRemoteData(), startWith(undefined))
)
).subscribe((results) => {
this.resultsRD$.next(results);
});
this.resultsRD$.next(results);
});
if (isEmpty(this.configuration$)) {
this.configuration$ = this.searchConfigService.getCurrentConfiguration('default');

View File

@@ -1,4 +1,4 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { AuthService } from '../../core/auth/auth.service';
import { FileService } from '../../core/shared/file.service';
import { of as observableOf } from 'rxjs';
@@ -77,7 +77,7 @@ describe('BitstreamDownloadPageComponent', () => {
}
describe('init', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
init();
initTestbed();
}));
@@ -93,7 +93,7 @@ describe('BitstreamDownloadPageComponent', () => {
describe('bitstream retrieval', () => {
describe('when the user is authorized and not logged in', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
init();
(authService.isAuthenticated as jasmine.Spy).and.returnValue(observableOf(false));
@@ -109,7 +109,7 @@ describe('BitstreamDownloadPageComponent', () => {
});
});
describe('when the user is authorized and logged in', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
init();
initTestbed();
}));
@@ -123,7 +123,7 @@ describe('BitstreamDownloadPageComponent', () => {
});
});
describe('when the user is not authorized and logged in', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
init();
(authorizationService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false));
initTestbed();
@@ -138,7 +138,7 @@ describe('BitstreamDownloadPageComponent', () => {
});
});
describe('when the user is not authorized and not logged in', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
init();
(authService.isAuthenticated as jasmine.Spy).and.returnValue(observableOf(false));
(authorizationService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false));

View File

@@ -7,10 +7,9 @@ import { Bitstream } from '../../core/shared/bitstream.model';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
import { AuthService } from '../../core/auth/auth.service';
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
import { combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs';
import { getBitstreamDownloadRoute, getForbiddenRoute } from '../../app-routing-paths';
import { TranslateService } from '@ngx-translate/core';
import { Subscription } from 'rxjs/internal/Subscription';
import { EPerson } from '../../core/eperson/models/eperson.model';
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import { ItemRequestDataService } from '../../core/data/item-request-data.service';

View File

@@ -25,7 +25,7 @@
</div>
<ul class="list-unstyled">
<li *ngFor="let object of objects?.payload?.page" class="mt-4 mb-4">
<ds-listable-object-component-loader [object]="object"></ds-listable-object-component-loader>
<ds-listable-object-component-loader [object]="object" [viewMode]="viewMode"></ds-listable-object-component-loader>
</li>
</ul>
<div>

View File

@@ -2,7 +2,7 @@ import { BrowseByComponent } from './browse-by.component';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { By } from '@angular/platform-browser';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
import { of as observableOf } from 'rxjs';
import { SharedModule } from '../shared.module';
import { CommonModule } from '@angular/common';
@@ -18,17 +18,31 @@ import { PaginationComponentOptions } from '../pagination/pagination-component-o
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils';
import { storeModuleConfig } from '../../app.reducer';
import { FindListOptions } from '../../core/data/request.models';
import { PaginationService } from '../../core/pagination/pagination.service';
import { PaginationServiceStub } from '../testing/pagination-service.stub';
import { ListableObjectComponentLoaderComponent } from '../object-collection/shared/listable-object/listable-object-component-loader.component';
import { ViewMode } from '../../core/shared/view-mode.model';
import { BrowseEntryListElementComponent } from '../object-list/browse-entry-list-element/browse-entry-list-element.component';
import {
DEFAULT_CONTEXT,
listableObjectComponent,
} from '../object-collection/shared/listable-object/listable-object.decorator';
import { BrowseEntry } from '../../core/shared/browse-entry.model';
import { ITEM } from '../../core/shared/item.resource-type';
import { ThemeService } from '../theme-support/theme.service';
import SpyObj = jasmine.SpyObj;
@listableObjectComponent(BrowseEntry, ViewMode.ListElement, DEFAULT_CONTEXT, 'custom')
@Component({
selector: 'ds-browse-entry-list-element',
template: ''
})
class MockThemedBrowseEntryListElementComponent {}
describe('BrowseByComponent', () => {
let comp: BrowseByComponent;
let fixture: ComponentFixture<BrowseByComponent>;
let themeService: ThemeService;
const mockItems = [
Object.assign(new Item(), {
id: 'fakeId-1',
@@ -59,9 +73,12 @@ describe('BrowseByComponent', () => {
});
const paginationService = new PaginationServiceStub(paginationConfig);
let themeService: SpyObj<ThemeService>;
beforeEach(waitForAsync(() => {
themeService = jasmine.createSpyObj('themeService', {
getThemeName: 'dspace',
getThemeName$: observableOf('dspace'),
});
TestBed.configureTestingModule({
imports: [
@@ -82,6 +99,7 @@ describe('BrowseByComponent', () => {
declarations: [],
providers: [
{provide: PaginationService, useValue: paginationService},
{provide: MockThemedBrowseEntryListElementComponent},
{ provide: ThemeService, useValue: themeService },
],
schemas: [NO_ERRORS_SCHEMA]
@@ -170,4 +188,67 @@ describe('BrowseByComponent', () => {
});
});
describe('when enableArrows is true and browseEntries are provided', () => {
let browseEntries;
beforeEach(() => {
browseEntries = [
Object.assign(new BrowseEntry(), {
type: ITEM,
authority: 'authority key 1',
value: 'browse entry 1',
language: null,
count: 1,
}),
Object.assign(new BrowseEntry(), {
type: ITEM,
authority: null,
value: 'browse entry 2',
language: null,
count: 4,
}),
];
comp.enableArrows = true;
comp.objects$ = createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), browseEntries));
comp.paginationConfig = paginationConfig;
comp.sortConfig = Object.assign(new SortOptions('dc.title', SortDirection.ASC));
// NOTE: do NOT trigger change detection until the theme is set, such that the theme can be picked up as well
});
describe('when theme is base', () => {
beforeEach(() => {
themeService.getThemeName.and.returnValue('base');
themeService.getThemeName$.and.returnValue(observableOf('base'));
fixture.detectChanges();
});
it('should use the base component to render browse entries', () => {
const componentLoaders = fixture.debugElement.queryAll(By.directive(ListableObjectComponentLoaderComponent));
expect(componentLoaders.length).toEqual(browseEntries.length);
componentLoaders.forEach((componentLoader) => {
const browseEntry = componentLoader.query(By.css('ds-browse-entry-list-element'));
expect(browseEntry.componentInstance).toBeInstanceOf(BrowseEntryListElementComponent);
});
});
});
describe('when theme is custom', () => {
beforeEach(() => {
themeService.getThemeName.and.returnValue('custom');
themeService.getThemeName$.and.returnValue(observableOf('custom'));
fixture.detectChanges();
});
it('should use the themed component to render browse entries', () => {
const componentLoaders = fixture.debugElement.queryAll(By.directive(ListableObjectComponentLoaderComponent));
expect(componentLoaders.length).toEqual(browseEntries.length);
componentLoaders.forEach((componentLoader) => {
const browseEntry = componentLoader.query(By.css('ds-browse-entry-list-element'));
expect(browseEntry.componentInstance).toBeInstanceOf(MockThemedBrowseEntryListElementComponent);
});
});
});
});
});

View File

@@ -8,6 +8,7 @@ import { Observable } from 'rxjs';
import { ListableObject } from '../object-collection/shared/listable-object.model';
import { getStartsWithComponent, StartsWithType } from '../starts-with/starts-with-decorator';
import { PaginationService } from '../../core/pagination/pagination.service';
import { ViewMode } from '../../core/shared/view-mode.model';
@Component({
selector: 'ds-browse-by',
@@ -22,6 +23,12 @@ import { PaginationService } from '../../core/pagination/pagination.service';
* Component to display a browse-by page for any ListableObject
*/
export class BrowseByComponent implements OnInit {
/**
* ViewMode that should be passed to {@link ListableObjectComponentLoaderComponent}.
*/
viewMode: ViewMode = ViewMode.ListElement;
/**
* The i18n message to display as title
*/

View File

@@ -8,7 +8,7 @@ import { ChipsItem } from './models/chips-item.model';
import { UploaderService } from '../uploader/uploader.service';
import { TranslateService } from '@ngx-translate/core';
import { Options } from 'sortablejs';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { BehaviorSubject } from 'rxjs';
@Component({
selector: 'ds-chips',

View File

@@ -11,8 +11,8 @@
aria-labelledby="dropdownMenuButton"
(scroll)="onScroll($event)"
infiniteScroll
[infiniteScrollDistance]="5"
[infiniteScrollThrottle]="300"
[infiniteScrollDistance]="1.5"
[infiniteScrollThrottle]="0"
[infiniteScrollUpDistance]="1.5"
[fromRoot]="true"
[scrollWindow]="false"
@@ -21,7 +21,7 @@
<button class="dropdown-item disabled" *ngIf="searchListCollection?.length == 0 && !(isLoading | async)">
{{'submission.sections.general.no-collection' | translate}}
</button>
<ng-container *ngIf="searchListCollection?.length > 0 && !(isLoading | async)">
<ng-container *ngIf="searchListCollection?.length > 0">
<button *ngFor="let listItem of searchListCollection"
class="dropdown-item collection-item"
title="{{ listItem.collection.name }}"

View File

@@ -223,20 +223,20 @@ export class CollectionDropdownComponent implements OnInit, OnDestroy {
switchMap((collectionsRD: RemoteData<PaginatedList<Collection>>) => {
this.searchComplete.emit();
if (collectionsRD.hasSucceeded && collectionsRD.payload.totalElements > 0) {
if ( (this.searchListCollection.length + findOptions.elementsPerPage) >= collectionsRD.payload.totalElements ) {
if (this.searchListCollection.length >= collectionsRD.payload.totalElements) {
this.hasNextPage = false;
this.emitSelectionEvents(collectionsRD);
return observableFrom(collectionsRD.payload.page).pipe(
mergeMap((collection: Collection) => collection.parentCommunity.pipe(
getFirstSucceededRemoteDataPayload(),
map((community: Community) => ({
communities: [{ id: community.id, name: community.name }],
collection: { id: collection.id, uuid: collection.id, name: collection.name }
})
))),
reduce((acc: any, value: any) => [...acc, value], []),
);
}
this.emitSelectionEvents(collectionsRD);
return observableFrom(collectionsRD.payload.page).pipe(
mergeMap((collection: Collection) => collection.parentCommunity.pipe(
getFirstSucceededRemoteDataPayload(),
map((community: Community) => ({
communities: [{ id: community.id, name: community.name }],
collection: { id: collection.id, uuid: collection.id, name: collection.name }
})
))),
reduce((acc: any, value: any) => [...acc, value], []),
);
} else {
this.hasNextPage = false;
return observableOf([]);

View File

@@ -1,7 +1,7 @@
import { Component, Input, OnInit } from '@angular/core';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
import { Observable } from 'rxjs/internal/Observable';
import { Observable } from 'rxjs';
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
@Component({

View File

@@ -1,11 +1,10 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
import { Observable } from 'rxjs/internal/Observable';
import { Observable, of } from 'rxjs';
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
import { VersionHistoryDataService } from '../../../core/data/version-history-data.service';
import { Item } from '../../../core/shared/item.model';
import { map, startWith, switchMap } from 'rxjs/operators';
import { of } from 'rxjs';
@Component({
selector: 'ds-dso-page-version-button',

View File

@@ -3,7 +3,7 @@
class="form-control"
(click)="$event.stopPropagation();"
placeholder="{{'dso-selector.placeholder' | translate: { type: typesString } }}"
[formControl]="input" dsAutoFocus (keyup.enter)="selectSingleResult()">
[formControl]="input" ngbAutofocus (keyup.enter)="selectSingleResult()">
</div>
<div class="dropdown-divider"></div>
<div class="scrollable-menu list-group">

View File

@@ -1,9 +1,8 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs/internal/Observable';
import { Observable, of as observableOf } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { of as observableOf } from 'rxjs';
import { METADATA_EXPORT_SCRIPT_NAME, ScriptDataService } from '../../../../core/data/processes/script-data.service';
import { Collection } from '../../../../core/shared/collection.model';
import { Community } from '../../../../core/shared/community.model';

View File

@@ -1,3 +1,4 @@
<div>
<label>{{ message }}</label>
</div>
<ds-alert [type]="AlertTypeEnum.Error" [dismissible]="false">
<!-- Using [innerHTML] instead of {{message}} allows to render HTML code -->
<span [innerHTML]="message"></span>
</ds-alert>

View File

@@ -36,7 +36,7 @@ describe('ErrorComponent (inline template)', () => {
comp = fixture.componentInstance; // ErrorComponent test instance
// query for the message <label> by CSS element selector
de = fixture.debugElement.query(By.css('label'));
de = fixture.debugElement.query(By.css('ds-alert'));
el = de.nativeElement;
});

View File

@@ -3,6 +3,7 @@ import { Component, Input } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { Subscription } from 'rxjs';
import { AlertType } from '../alert/aletr-type';
@Component({
selector: 'ds-error',
@@ -13,6 +14,12 @@ export class ErrorComponent {
@Input() message = 'Error...';
/**
* The AlertType enumeration
* @type {AlertType}
*/
public AlertTypeEnum = AlertType;
private subscription: Subscription;
constructor(private translate: TranslateService) {

View File

@@ -1,4 +1,4 @@
<a [routerLink]="(bitstreamPath$| async)?.routerLink" [queryParams]="(bitstreamPath$| async)?.queryParams" [target]="isBlank ? '_blank': '_self'" [ngClass]="cssClasses">
<a [routerLink]="(bitstreamPath$| async)?.routerLink" class="dont-break-out" [queryParams]="(bitstreamPath$| async)?.queryParams" [target]="isBlank ? '_blank': '_self'" [ngClass]="cssClasses">
<span *ngIf="!(canDownload$ |async)"><i class="fas fa-lock"></i></span>
<ng-container *ngTemplateOutlet="content"></ng-container>
</a>

View File

@@ -7,59 +7,64 @@
</div>
<div class="modal-body">
<ds-loading *ngIf="!item || !collection"></ds-loading>
<ngb-tabset *ngIf="item && collection">
<ngb-tab [title]="'submission.sections.describe.relationship-lookup.search-tab.tab-title.' + relationshipOptions.relationshipType | translate : {count: (totalInternal$ | async)}">
<ng-template ngbTabContent>
<ds-dynamic-lookup-relation-search-tab
[selection$]="selection$"
[listId]="listId"
[relationship]="relationshipOptions"
[repeatable]="repeatable"
[context]="context"
[query]="query"
[relationshipType]="relationshipType"
[isLeft]="isLeft"
[item]="item"
[isEditRelationship]="isEditRelationship"
[toRemove]="toRemove"
(selectObject)="select($event)"
(deselectObject)="deselect($event)"
class="d-block pt-3">
</ds-dynamic-lookup-relation-search-tab>
</ng-template>
</ngb-tab>
<ngb-tab *ngFor="let source of (externalSourcesRD$ | async); let idx = index"
[title]="'submission.sections.describe.relationship-lookup.search-tab.tab-title.' + source.id | translate : {count: (totalExternal$ | async)[idx]}">
<ng-template ngbTabContent>
<ds-dynamic-lookup-relation-external-source-tab
[label]="label"
[listId]="listId"
[repeatable]="repeatable"
[item]="item"
[collection]="collection"
[relationship]="relationshipOptions"
[context]="context"
[externalSource]="source"
(importedObject)="imported($event)"
class="d-block pt-3">
</ds-dynamic-lookup-relation-external-source-tab>
</ng-template>
</ngb-tab>
<ngb-tab *ngIf="!isEditRelationship" [title]="'submission.sections.describe.relationship-lookup.selection-tab.tab-title' | translate : {count: (selection$ | async)?.length}">
<ng-template ngbTabContent>
<ds-dynamic-lookup-relation-selection-tab
[selection$]="selection$"
[listId]="listId"
[relationshipType]="relationshipOptions.relationshipType"
[repeatable]="repeatable"
[context]="context"
(selectObject)="select($event)"
(deselectObject)="deselect($event)"
class="d-block pt-3">
</ds-dynamic-lookup-relation-selection-tab>
</ng-template>
</ngb-tab>
</ngb-tabset>
<ng-container *ngIf="item && collection">
<ul ngbNav #nav="ngbNav" class="nav-tabs">
<li ngbNavItem>
<a ngbNavLink>{{'submission.sections.describe.relationship-lookup.search-tab.tab-title.' + relationshipOptions.relationshipType | translate : { count: (totalInternal$ | async)} }}</a>
<ng-template ngbNavContent>
<ds-dynamic-lookup-relation-search-tab
[selection$]="selection$"
[listId]="listId"
[relationship]="relationshipOptions"
[repeatable]="repeatable"
[context]="context"
[query]="query"
[relationshipType]="relationshipType"
[isLeft]="isLeft"
[item]="item"
[isEditRelationship]="isEditRelationship"
[toRemove]="toRemove"
(selectObject)="select($event)"
(deselectObject)="deselect($event)"
class="d-block pt-3">
</ds-dynamic-lookup-relation-search-tab>
</ng-template>
</li>
<li ngbNavItem *ngFor="let source of (externalSourcesRD$ | async); let idx = index">
<a ngbNavLink>{{'submission.sections.describe.relationship-lookup.search-tab.tab-title.' + source.id | translate : { count: (totalExternal$ | async)[idx] } }}</a>
<ng-template ngbNavContent>
<ds-dynamic-lookup-relation-external-source-tab
[label]="label"
[listId]="listId"
[repeatable]="repeatable"
[item]="item"
[collection]="collection"
[relationship]="relationshipOptions"
[context]="context"
[externalSource]="source"
(importedObject)="imported($event)"
class="d-block pt-3">
</ds-dynamic-lookup-relation-external-source-tab>
</ng-template>
</li>
<li ngbNavItem *ngIf="!isEditRelationship">
<a ngbNavLink>{{'submission.sections.describe.relationship-lookup.selection-tab.tab-title' | translate : { count: (selection$ | async)?.length } }}</a>
<ng-template ngbNavContent>
<ds-dynamic-lookup-relation-selection-tab
[selection$]="selection$"
[listId]="listId"
[relationshipType]="relationshipOptions.relationshipType"
[repeatable]="repeatable"
[context]="context"
(selectObject)="select($event)"
(deselectObject)="deselect($event)"
class="d-block pt-3">
</ds-dynamic-lookup-relation-selection-tab>
</ng-template>
</li>
</ul>
<div [ngbNavOutlet]="nav"></div>
</ng-container>
</div>
<div class="modal-footer">
<small>{{ ('submission.sections.describe.relationship-lookup.selected' | translate: {size: (selection$ | async)?.length || 0}) }}</small>

View File

@@ -6,7 +6,7 @@ import {
} from './metadata-representation.decorator';
import { MetadataRepresentationType } from '../../core/shared/metadata-representation/metadata-representation.model';
import { Context } from '../../core/shared/context.model';
import * as uuidv4 from 'uuid/v4';
import { v4 as uuidv4 } from 'uuid';
import { environment } from '../../../environments/environment';
let ogEnvironmentThemes;

View File

@@ -1,7 +1,7 @@
import { Component, Injector } from '@angular/core';
import { ClaimedTaskActionsAbstractComponent } from '../abstract/claimed-task-actions-abstract.component';
import { rendersWorkflowTaskOption } from '../switcher/claimed-task-actions-decorator';
import { Observable } from 'rxjs';
import { Observable, of } from 'rxjs';
import { RemoteData } from '../../../../core/data/remote-data';
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
import { Router } from '@angular/router';
@@ -10,7 +10,6 @@ import { TranslateService } from '@ngx-translate/core';
import { SearchService } from '../../../../core/shared/search/search.service';
import { RequestService } from '../../../../core/data/request.service';
import { ClaimedApprovedTaskSearchResult } from '../../../object-collection/shared/claimed-approved-task-search-result.model';
import { of } from 'rxjs/internal/observable/of';
export const WORKFLOW_TASK_OPTION_APPROVE = 'submit_approve';

View File

@@ -9,10 +9,9 @@ import { NotificationsService } from '../../../notifications/notifications.servi
import { TranslateService } from '@ngx-translate/core';
import { SearchService } from '../../../../core/shared/search/search.service';
import { RequestService } from '../../../../core/data/request.service';
import { Observable } from 'rxjs';
import { Observable, of } from 'rxjs';
import { RemoteData } from '../../../../core/data/remote-data';
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
import { of } from 'rxjs/internal/observable/of';
import { ClaimedDeclinedTaskSearchResult } from '../../../object-collection/shared/claimed-declined-task-search-result.model';
export const WORKFLOW_TASK_OPTION_REJECT = 'submit_reject';

View File

@@ -12,8 +12,7 @@ import { NotificationsService } from '../notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
import { RequestService } from '../../core/data/request.service';
import { SearchService } from '../../core/shared/search/search.service';
import { Observable} from 'rxjs/internal/Observable';
import { of} from 'rxjs/internal/observable/of';
import { Observable, of } from 'rxjs';
import { ProcessTaskResponse } from '../../core/tasks/models/process-task-response';
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
import { getSearchResultFor } from '../search/search-result-element-decorator';

View File

@@ -20,7 +20,7 @@ import { GenericConstructor } from '../../../../core/shared/generic-constructor'
import { ListableObjectDirective } from './listable-object.directive';
import { CollectionElementLinkType } from '../../collection-element-link.type';
import { hasValue, isNotEmpty } from '../../../empty.util';
import { Subscription } from 'rxjs/internal/Subscription';
import { Subscription } from 'rxjs';
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
import { take } from 'rxjs/operators';
import { ThemeService } from '../../../theme-support/theme.service';

View File

@@ -1,3 +1,3 @@
<div>
<span>{{metadataRepresentation.getValue()}}</span>
<span class="dont-break-out">{{metadataRepresentation.getValue()}}</span>
</div>

View File

@@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { of as observableOf } from 'rxjs';
@@ -60,7 +60,7 @@ mockResultObject.indexableObject = Object.assign(new ClaimedTask(), { workflowit
const linkService = getMockLinkService();
describe('ClaimedApprovedSearchResultListElementComponent', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [NoopAnimationsModule],
declarations: [ClaimedApprovedSearchResultListElementComponent, VarDirective],
@@ -75,7 +75,7 @@ describe('ClaimedApprovedSearchResultListElementComponent', () => {
}).compileComponents();
}));
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(ClaimedApprovedSearchResultListElementComponent);
component = fixture.componentInstance;
}));

View File

@@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { of as observableOf } from 'rxjs';
@@ -60,7 +60,7 @@ mockResultObject.indexableObject = Object.assign(new ClaimedTask(), { workflowit
const linkService = getMockLinkService();
describe('ClaimedDeclinedSearchResultListElementComponent', () => {
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [NoopAnimationsModule],
declarations: [ClaimedDeclinedSearchResultListElementComponent, VarDirective],
@@ -75,7 +75,7 @@ describe('ClaimedDeclinedSearchResultListElementComponent', () => {
}).compileComponents();
}));
beforeEach(async(() => {
beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(ClaimedDeclinedSearchResultListElementComponent);
component = fixture.componentInstance;
}));

View File

@@ -2,9 +2,9 @@
<ds-truncatable [id]="dso.id" *ngIf="object !== undefined && object !== null">
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
[routerLink]="[itemPageRoute]" class="lead item-list-title"
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out"
[innerHTML]="dsoTitle"></a>
<span *ngIf="linkType == linkTypes.None" class="lead item-list-title"
<span *ngIf="linkType == linkTypes.None" class="lead item-list-title dont-break-out"
[innerHTML]="dsoTitle"></span>
<span class="text-muted">
<ds-truncatable-part [id]="dso.id" [minLines]="1">

View File

@@ -3,14 +3,13 @@ import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { SearchResultListElementComponent } from '../search-result-list-element/search-result-list-element.component';
import { Component } from '@angular/core';
import { hasValue, isNotEmpty } from '../../empty.util';
import { Observable } from 'rxjs/internal/Observable';
import { Observable, of as observableOf } from 'rxjs';
import { TruncatableService } from '../../truncatable/truncatable.service';
import { LinkService } from '../../../core/cache/builders/link.service';
import { find, map } from 'rxjs/operators';
import { ChildHALResource } from '../../../core/shared/child-hal-resource.model';
import { followLink } from '../../utils/follow-link-config.model';
import { RemoteData } from '../../../core/data/remote-data';
import { of as observableOf } from 'rxjs';
import { Context } from '../../../core/shared/context.model';
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';

Some files were not shown because too many files have changed in this diff Show More