Cache redesign part 1, and add support for alternative links

This commit is contained in:
Art Lowel
2020-12-11 14:18:44 +01:00
parent f4853972cc
commit 4e18fa35ca
522 changed files with 7537 additions and 6933 deletions

View File

@@ -65,6 +65,13 @@ jobs:
- name: Run specs (unit tests) - name: Run specs (unit tests)
run: yarn run test:headless run: yarn run test:headless
# NOTE: Angular CLI only supports code coverage for specs. See https://github.com/angular/angular-cli/issues/6286
# Upload coverage reports to Codecov (for Node v12 only)
# https://github.com/codecov/codecov-action
- name: Upload coverage to Codecov.io
uses: codecov/codecov-action@v1
if: matrix.node-version == '12.x'
# Using docker-compose start backend using CI configuration # Using docker-compose start backend using CI configuration
# and load assetstore from a cached copy # and load assetstore from a cached copy
- name: Start DSpace REST Backend via Docker (for e2e tests) - name: Start DSpace REST Backend via Docker (for e2e tests)
@@ -78,10 +85,3 @@ jobs:
- name: Shutdown Docker containers - name: Shutdown Docker containers
run: docker-compose -f ./docker/docker-compose-ci.yml down run: docker-compose -f ./docker/docker-compose-ci.yml down
# NOTE: Angular CLI only supports code coverage for specs. See https://github.com/angular/angular-cli/issues/6286
# Upload coverage reports to Codecov (for Node v12 only)
# https://github.com/codecov/codecov-action
- name: Upload coverage to Codecov.io
uses: codecov/codecov-action@v1
if: matrix.node-version == '12.x'

View File

@@ -94,6 +94,12 @@
"polyfills": "src/polyfills.ts", "polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json", "tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js", "karmaConfig": "karma.conf.js",
"sourceMap": {
"scripts": false,
"styles": false,
"hidden": false,
"vendor": false
},
"assets": [ "assets": [
"src/assets" "src/assets"
], ],

View File

@@ -8,7 +8,7 @@ import { BrowserModule, By } from '@angular/platform-browser';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginatedList, buildPaginatedList } from '../../../core/data/paginated-list.model';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { FindListOptions } from '../../../core/data/request.models'; import { FindListOptions } from '../../../core/data/request.models';
import { EPersonDataService } from '../../../core/eperson/eperson-data.service'; import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
@@ -44,7 +44,7 @@ describe('EPeopleRegistryComponent', () => {
activeEPerson: null, activeEPerson: null,
allEpeople: mockEPeople, allEpeople: mockEPeople,
getEPeople(): Observable<RemoteData<PaginatedList<EPerson>>> { getEPeople(): Observable<RemoteData<PaginatedList<EPerson>>> {
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: this.allEpeople.length, totalElements: this.allEpeople.length, totalPages: 1, currentPage: 1 }), this.allEpeople)); return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo({ elementsPerPage: this.allEpeople.length, totalElements: this.allEpeople.length, totalPages: 1, currentPage: 1 }), this.allEpeople));
}, },
getActiveEPerson(): Observable<EPerson> { getActiveEPerson(): Observable<EPerson> {
return observableOf(this.activeEPerson); return observableOf(this.activeEPerson);
@@ -54,18 +54,18 @@ describe('EPeopleRegistryComponent', () => {
const result = this.allEpeople.find((ePerson: EPerson) => { const result = this.allEpeople.find((ePerson: EPerson) => {
return ePerson.email === query return ePerson.email === query
}); });
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: [result].length, totalElements: [result].length, totalPages: 1, currentPage: 1 }), [result])); return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo({ elementsPerPage: [result].length, totalElements: [result].length, totalPages: 1, currentPage: 1 }), [result]));
} }
if (scope === 'metadata') { if (scope === 'metadata') {
if (query === '') { if (query === '') {
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: this.allEpeople.length, totalElements: this.allEpeople.length, totalPages: 1, currentPage: 1 }), this.allEpeople)); return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo({ elementsPerPage: this.allEpeople.length, totalElements: this.allEpeople.length, totalPages: 1, currentPage: 1 }), this.allEpeople));
} }
const result = this.allEpeople.find((ePerson: EPerson) => { const result = this.allEpeople.find((ePerson: EPerson) => {
return (ePerson.name.includes(query) || ePerson.email.includes(query)) return (ePerson.name.includes(query) || ePerson.email.includes(query))
}); });
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: [result].length, totalElements: [result].length, totalPages: 1, currentPage: 1 }), [result])); return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo({ elementsPerPage: [result].length, totalElements: [result].length, totalPages: 1, currentPage: 1 }), [result]));
} }
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: this.allEpeople.length, totalElements: this.allEpeople.length, totalPages: 1, currentPage: 1 }), this.allEpeople)); return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo({ elementsPerPage: this.allEpeople.length, totalElements: this.allEpeople.length, totalPages: 1, currentPage: 1 }), this.allEpeople));
}, },
deleteEPerson(ePerson: EPerson): Observable<boolean> { deleteEPerson(ePerson: EPerson): Observable<boolean> {
this.allEpeople = this.allEpeople.filter((ePerson2: EPerson) => { this.allEpeople = this.allEpeople.filter((ePerson2: EPerson) => {

View File

@@ -5,7 +5,7 @@ import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { Subscription } from 'rxjs/internal/Subscription'; import { Subscription } from 'rxjs/internal/Subscription';
import { map, switchMap, take } from 'rxjs/operators'; import { map, switchMap, take } from 'rxjs/operators';
import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginatedList, buildPaginatedList } from '../../../core/data/paginated-list.model';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { EPersonDataService } from '../../../core/eperson/eperson-data.service'; import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
import { EPerson } from '../../../core/eperson/models/eperson.model'; import { EPerson } from '../../../core/eperson/models/eperson.model';
@@ -15,13 +15,16 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio
import { EpersonDtoModel } from '../../../core/eperson/models/eperson-dto.model'; import { EpersonDtoModel } from '../../../core/eperson/models/eperson-dto.model';
import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
import { getAllSucceededRemoteDataPayload } from '../../../core/shared/operators'; import {
import { ErrorResponse, RestResponse } from '../../../core/cache/response.models'; getAllSucceededRemoteDataPayload,
getFirstCompletedRemoteData
} from '../../../core/shared/operators';
import { ConfirmationModalComponent } from '../../../shared/confirmation-modal/confirmation-modal.component'; import { ConfirmationModalComponent } from '../../../shared/confirmation-modal/confirmation-modal.component';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { RequestService } from '../../../core/data/request.service'; import { RequestService } from '../../../core/data/request.service';
import { filter } from 'rxjs/internal/operators/filter'; import { filter } from 'rxjs/internal/operators/filter';
import { PageInfo } from '../../../core/shared/page-info.model'; import { PageInfo } from '../../../core/shared/page-info.model';
import { NoContent } from '../../../core/shared/NoContent.model';
@Component({ @Component({
selector: 'ds-epeople-registry', selector: 'ds-epeople-registry',
@@ -159,7 +162,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
}) })
); );
})).pipe(map((dtos: EpersonDtoModel[]) => { })).pipe(map((dtos: EpersonDtoModel[]) => {
return new PaginatedList(epeople.pageInfo, dtos); return buildPaginatedList(epeople.pageInfo, dtos);
})) }))
})).subscribe((value) => { })).subscribe((value) => {
this.ePeopleDto$.next(value); this.ePeopleDto$.next(value);
@@ -215,13 +218,12 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => { modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => {
if (confirm) { if (confirm) {
if (hasValue(ePerson.id)) { if (hasValue(ePerson.id)) {
this.epersonService.deleteEPerson(ePerson).pipe(take(1)).subscribe((restResponse: RestResponse) => { this.epersonService.deleteEPerson(ePerson).pipe(getFirstCompletedRemoteData()).subscribe((restResponse: RemoteData<NoContent>) => {
if (restResponse.isSuccessful) { if (restResponse.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', { name: ePerson.name })); this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', { name: ePerson.name }));
this.reset(); this.reset();
} else { } else {
const errorResponse = restResponse as ErrorResponse; this.notificationsService.error('Error occured when trying to delete EPerson with id: ' + ePerson.id + ' with code: ' + restResponse.statusCode + ' and message: ' + restResponse.errorMessage);
this.notificationsService.error('Error occured when trying to delete EPerson with id: ' + ePerson.id + ' with code: ' + errorResponse.statusCode + ' and message: ' + errorResponse.errorMessage);
} }
}) })
}} }}

View File

@@ -1,14 +1,13 @@
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NO_ERRORS_SCHEMA } from '@angular/core'; import { NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule, By } from '@angular/platform-browser'; import { BrowserModule, By } from '@angular/platform-browser';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { RestResponse } from '../../../../core/cache/response.models'; import { PaginatedList, buildPaginatedList } from '../../../../core/data/paginated-list.model';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { RemoteData } from '../../../../core/data/remote-data'; import { RemoteData } from '../../../../core/data/remote-data';
import { FindListOptions } from '../../../../core/data/request.models'; import { FindListOptions } from '../../../../core/data/request.models';
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
@@ -46,7 +45,7 @@ describe('EPersonFormComponent', () => {
activeEPerson: null, activeEPerson: null,
allEpeople: mockEPeople, allEpeople: mockEPeople,
getEPeople(): Observable<RemoteData<PaginatedList<EPerson>>> { getEPeople(): Observable<RemoteData<PaginatedList<EPerson>>> {
return createSuccessfulRemoteDataObject$(new PaginatedList(null, this.allEpeople)); return createSuccessfulRemoteDataObject$(buildPaginatedList(null, this.allEpeople));
}, },
getActiveEPerson(): Observable<EPerson> { getActiveEPerson(): Observable<EPerson> {
return observableOf(this.activeEPerson); return observableOf(this.activeEPerson);
@@ -56,18 +55,18 @@ describe('EPersonFormComponent', () => {
const result = this.allEpeople.find((ePerson: EPerson) => { const result = this.allEpeople.find((ePerson: EPerson) => {
return ePerson.email === query return ePerson.email === query
}); });
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [result])); return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [result]));
} }
if (scope === 'metadata') { if (scope === 'metadata') {
if (query === '') { if (query === '') {
return createSuccessfulRemoteDataObject$(new PaginatedList(null, this.allEpeople)); return createSuccessfulRemoteDataObject$(buildPaginatedList(null, this.allEpeople));
} }
const result = this.allEpeople.find((ePerson: EPerson) => { const result = this.allEpeople.find((ePerson: EPerson) => {
return (ePerson.name.includes(query) || ePerson.email.includes(query)) return (ePerson.name.includes(query) || ePerson.email.includes(query))
}); });
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [result])); return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [result]));
} }
return createSuccessfulRemoteDataObject$(new PaginatedList(null, this.allEpeople)); return createSuccessfulRemoteDataObject$(buildPaginatedList(null, this.allEpeople));
}, },
deleteEPerson(ePerson: EPerson): Observable<boolean> { deleteEPerson(ePerson: EPerson): Observable<boolean> {
this.allEpeople = this.allEpeople.filter((ePerson2: EPerson) => { this.allEpeople = this.allEpeople.filter((ePerson2: EPerson) => {
@@ -75,8 +74,9 @@ describe('EPersonFormComponent', () => {
}); });
return observableOf(true); return observableOf(true);
}, },
create(ePerson: EPerson) { create(ePerson: EPerson): Observable<RemoteData<EPerson>> {
this.allEpeople = [...this.allEpeople, ePerson] this.allEpeople = [...this.allEpeople, ePerson];
return createSuccessfulRemoteDataObject$(ePerson);
}, },
editEPerson(ePerson: EPerson) { editEPerson(ePerson: EPerson) {
this.activeEPerson = ePerson; this.activeEPerson = ePerson;
@@ -87,18 +87,13 @@ describe('EPersonFormComponent', () => {
clearEPersonRequests(): void { clearEPersonRequests(): void {
// empty // empty
}, },
tryToCreate(ePerson: EPerson): Observable<RestResponse> { updateEPerson(ePerson: EPerson): Observable<RemoteData<EPerson>> {
this.allEpeople = [...this.allEpeople, ePerson]
return observableOf(new RestResponse(true, 200, 'Success'));
},
updateEPerson(ePerson: EPerson): Observable<RestResponse> {
this.allEpeople.forEach((ePersonInList: EPerson, i: number) => { this.allEpeople.forEach((ePersonInList: EPerson, i: number) => {
if (ePersonInList.id === ePerson.id) { if (ePersonInList.id === ePerson.id) {
this.allEpeople[i] = ePerson; this.allEpeople[i] = ePerson;
} }
}); });
return observableOf(new RestResponse(true, 200, 'Success')); return createSuccessfulRemoteDataObject$(ePerson);
} }
}; };
builderService = getMockFormBuilderService(); builderService = getMockFormBuilderService();
@@ -299,7 +294,7 @@ describe('EPersonFormComponent', () => {
it ('should call the epersonFormComponent delete when clicked on the button' , () => { it ('should call the epersonFormComponent delete when clicked on the button' , () => {
spyOn(component, 'delete').and.stub(); spyOn(component, 'delete').and.stub();
spyOn(component.epersonService, 'deleteEPerson').and.returnValue(observableOf(new RestResponse(true, 204, 'No Content'))); spyOn(component.epersonService, 'deleteEPerson').and.returnValue(createSuccessfulRemoteDataObject$('No Content', 204));
const deleteButton = fixture.debugElement.query(By.css('.delete-button')); const deleteButton = fixture.debugElement.query(By.css('.delete-button'));
deleteButton.triggerEventHandler('click', null); deleteButton.triggerEventHandler('click', null);
expect(component.delete).toHaveBeenCalled(); expect(component.delete).toHaveBeenCalled();
@@ -307,7 +302,7 @@ describe('EPersonFormComponent', () => {
it ('should call the epersonService delete when clicked on the button' , () => { it ('should call the epersonService delete when clicked on the button' , () => {
// ePersonDataServiceStub.activeEPerson = eperson; // ePersonDataServiceStub.activeEPerson = eperson;
spyOn(component.epersonService, 'deleteEPerson').and.returnValue(observableOf(new RestResponse(true, 204, 'No Content'))); spyOn(component.epersonService, 'deleteEPerson').and.returnValue(createSuccessfulRemoteDataObject$('No Content', 204));
const deleteButton = fixture.debugElement.query(By.css('.delete-button')); const deleteButton = fixture.debugElement.query(By.css('.delete-button'));
expect(deleteButton.nativeElement.disabled).toBe(false); expect(deleteButton.nativeElement.disabled).toBe(false);
deleteButton.triggerEventHandler('click', null); deleteButton.triggerEventHandler('click', null);

View File

@@ -10,14 +10,17 @@ import { TranslateService } from '@ngx-translate/core';
import { Subscription, combineLatest, of } from 'rxjs'; import { Subscription, combineLatest, of } from 'rxjs';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { switchMap, take } from 'rxjs/operators'; import { switchMap, take } from 'rxjs/operators';
import { RestResponse } from '../../../../core/cache/response.models'; import { PaginatedList } from '../../../../core/data/paginated-list.model';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { RemoteData } from '../../../../core/data/remote-data'; import { RemoteData } from '../../../../core/data/remote-data';
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
import { GroupDataService } from '../../../../core/eperson/group-data.service'; import { GroupDataService } from '../../../../core/eperson/group-data.service';
import { EPerson } from '../../../../core/eperson/models/eperson.model'; import { EPerson } from '../../../../core/eperson/models/eperson.model';
import { Group } from '../../../../core/eperson/models/group.model'; import { Group } from '../../../../core/eperson/models/group.model';
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators'; import {
getRemoteDataPayload,
getFirstSucceededRemoteData,
getFirstCompletedRemoteData
} from '../../../../core/shared/operators';
import { hasValue } from '../../../../shared/empty.util'; import { hasValue } from '../../../../shared/empty.util';
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
import { NotificationsService } from '../../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../../shared/notifications/notifications.service';
@@ -28,6 +31,7 @@ import { FeatureID } from '../../../../core/data/feature-authorization/feature-i
import { ConfirmationModalComponent } from '../../../../shared/confirmation-modal/confirmation-modal.component'; import { ConfirmationModalComponent } from '../../../../shared/confirmation-modal/confirmation-modal.component';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { RequestService } from '../../../../core/data/request.service'; import { RequestService } from '../../../../core/data/request.service';
import { NoContent } from '../../../../core/shared/NoContent.model';
@Component({ @Component({
selector: 'ds-eperson-form', selector: 'ds-eperson-form',
@@ -314,9 +318,11 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
createNewEPerson(values) { createNewEPerson(values) {
const ePersonToCreate = Object.assign(new EPerson(), values); const ePersonToCreate = Object.assign(new EPerson(), values);
const response = this.epersonService.tryToCreate(ePersonToCreate); const response = this.epersonService.create(ePersonToCreate);
response.pipe(take(1)).subscribe((restResponse: RestResponse) => { response.pipe(
if (restResponse.isSuccessful) { getFirstCompletedRemoteData()
).subscribe((rd: RemoteData<EPerson>) => {
if (rd.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.created.success', { name: ePersonToCreate.name })); this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.created.success', { name: ePersonToCreate.name }));
this.submitForm.emit(ePersonToCreate); this.submitForm.emit(ePersonToCreate);
} else { } else {
@@ -354,8 +360,8 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
}); });
const response = this.epersonService.updateEPerson(editedEperson); const response = this.epersonService.updateEPerson(editedEperson);
response.pipe(take(1)).subscribe((restResponse: RestResponse) => { response.pipe(take(1)).subscribe((rd: RemoteData<EPerson>) => {
if (restResponse.isSuccessful) { if (rd.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.edited.success', { name: editedEperson.name })); this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.edited.success', { name: editedEperson.name }));
this.submitForm.emit(editedEperson); this.submitForm.emit(editedEperson);
} else { } else {
@@ -380,7 +386,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
this.subs.push(this.epersonService.searchByScope('email', ePerson.email, { this.subs.push(this.epersonService.searchByScope('email', ePerson.email, {
currentPage: 1, currentPage: 1,
elementsPerPage: 0 elementsPerPage: 0
}).pipe(getSucceededRemoteData(), getRemoteDataPayload()) }).pipe(getFirstSucceededRemoteData(), getRemoteDataPayload())
.subscribe((list: PaginatedList<EPerson>) => { .subscribe((list: PaginatedList<EPerson>) => {
if (list.totalElements > 0) { if (list.totalElements > 0) {
this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.' + notificationSection + '.failure.emailInUse', { this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.' + notificationSection + '.failure.emailInUse', {
@@ -434,12 +440,12 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => { modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => {
if (confirm) { if (confirm) {
if (hasValue(eperson.id)) { if (hasValue(eperson.id)) {
this.epersonService.deleteEPerson(eperson).pipe(take(1)).subscribe((restResponse: RestResponse) => { this.epersonService.deleteEPerson(eperson).pipe(take(1)).subscribe((restResponse: RemoteData<NoContent>) => {
if (restResponse.isSuccessful) { if (restResponse.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', { name: eperson.name })); this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', { name: eperson.name }));
this.reset(); this.reset();
} else { } else {
this.notificationsService.error('Error occured when trying to delete EPerson with id: ' + eperson.id + ' with code: ' + restResponse.statusCode + ' and message: ' + restResponse.statusText); this.notificationsService.error('Error occured when trying to delete EPerson with id: ' + eperson.id + ' with code: ' + restResponse.statusCode + ' and message: ' + restResponse.errorMessage);
} }
this.cancelForm.emit(); this.cancelForm.emit();
}) })

View File

@@ -12,11 +12,10 @@ import { of as observableOf } from 'rxjs';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; import { ObjectCacheService } from '../../../../core/cache/object-cache.service';
import { RestResponse } from '../../../../core/cache/response.models';
import { DSOChangeAnalyzer } from '../../../../core/data/dso-change-analyzer.service'; import { DSOChangeAnalyzer } from '../../../../core/data/dso-change-analyzer.service';
import { DSpaceObjectDataService } from '../../../../core/data/dspace-object-data.service'; import { DSpaceObjectDataService } from '../../../../core/data/dspace-object-data.service';
import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service'; import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service';
import { PaginatedList } from '../../../../core/data/paginated-list'; import { PaginatedList, buildPaginatedList } from '../../../../core/data/paginated-list.model';
import { RemoteData } from '../../../../core/data/remote-data'; import { RemoteData } from '../../../../core/data/remote-data';
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
import { GroupDataService } from '../../../../core/eperson/group-data.service'; import { GroupDataService } from '../../../../core/eperson/group-data.service';
@@ -35,6 +34,7 @@ import { getMockTranslateService } from '../../../../shared/mocks/translate.serv
import { TranslateLoaderMock } from '../../../../shared/testing/translate-loader.mock'; import { TranslateLoaderMock } from '../../../../shared/testing/translate-loader.mock';
import { RouterMock } from '../../../../shared/mocks/router.mock'; import { RouterMock } from '../../../../shared/mocks/router.mock';
import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub'; import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
import { Operation } from 'fast-json-patch';
describe('GroupFormComponent', () => { describe('GroupFormComponent', () => {
let component: GroupFormComponent; let component: GroupFormComponent;
@@ -71,6 +71,7 @@ describe('GroupFormComponent', () => {
groupsDataServiceStub = { groupsDataServiceStub = {
allGroups: groups, allGroups: groups,
activeGroup: null, activeGroup: null,
createdGroup: null,
getActiveGroup(): Observable<Group> { getActiveGroup(): Observable<Group> {
return observableOf(this.activeGroup); return observableOf(this.activeGroup);
}, },
@@ -80,7 +81,10 @@ describe('GroupFormComponent', () => {
editGroup(group: Group) { editGroup(group: Group) {
this.activeGroup = group this.activeGroup = group
}, },
updateGroup(group: Group) { clearGroupsRequests() {
return null;
},
patch(group: Group, operations: Operation[]) {
return null; return null;
}, },
cancelEditGroup(): void { cancelEditGroup(): void {
@@ -89,12 +93,21 @@ describe('GroupFormComponent', () => {
findById(id: string) { findById(id: string) {
return observableOf({ payload: null, hasSucceeded: true }); return observableOf({ payload: null, hasSucceeded: true });
}, },
tryToCreate(group: Group): Observable<RestResponse> { findByHref(href: string) {
this.allGroups = [...this.allGroups, group] return createSuccessfulRemoteDataObject$(this.createdGroup);
return observableOf(new RestResponse(true, 200, 'Success')); },
create(group: Group): Observable<RemoteData<Group>> {
this.allGroups = [...this.allGroups, group];
this.createdGroup = Object.assign({}, group, {
_links: { self: { href: 'group-selflink' } }
});
return createSuccessfulRemoteDataObject$(this.createdGroup);
}, },
searchGroups(query: string): Observable<RemoteData<PaginatedList<Group>>> { searchGroups(query: string): Observable<RemoteData<PaginatedList<Group>>> {
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])) return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []))
},
getGroupEditPageRouterLinkWithID(id: string) {
return `group-edit-page-for-${id}`;
} }
}; };
authorizationService = jasmine.createSpyObj('authorizationService', { authorizationService = jasmine.createSpyObj('authorizationService', {
@@ -171,7 +184,7 @@ describe('GroupFormComponent', () => {
describe('with active Group', () => { describe('with active Group', () => {
beforeEach(() => { beforeEach(() => {
spyOn(groupsDataServiceStub, 'getActiveGroup').and.returnValue(observableOf(expected)); spyOn(groupsDataServiceStub, 'getActiveGroup').and.returnValue(observableOf(expected));
spyOn(groupsDataServiceStub, 'updateGroup').and.returnValue(observableOf(new RestResponse(true, 200, 'OK'))); spyOn(groupsDataServiceStub, 'patch').and.returnValue(createSuccessfulRemoteDataObject$(expected));
component.groupName.value = 'newGroupName'; component.groupName.value = 'newGroupName';
component.onSubmit(); component.onSubmit();
fixture.detectChanges(); fixture.detectChanges();

View File

@@ -9,15 +9,20 @@ import {
DynamicTextAreaModel DynamicTextAreaModel
} from '@ng-dynamic-forms/core'; } from '@ng-dynamic-forms/core';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { ObservedValueOf, combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs'; import {
ObservedValueOf,
combineLatest as observableCombineLatest,
Observable,
of as observableOf,
Subscription
} from 'rxjs';
import { catchError, map, switchMap, take } from 'rxjs/operators'; import { catchError, map, switchMap, take } from 'rxjs/operators';
import { getCollectionEditRolesRoute } from '../../../../+collection-page/collection-page-routing-paths'; import { getCollectionEditRolesRoute } from '../../../../+collection-page/collection-page-routing-paths';
import { getCommunityEditRolesRoute } from '../../../../+community-page/community-page-routing-paths'; import { getCommunityEditRolesRoute } from '../../../../+community-page/community-page-routing-paths';
import { RestResponse } from '../../../../core/cache/response.models';
import { DSpaceObjectDataService } from '../../../../core/data/dspace-object-data.service'; import { DSpaceObjectDataService } from '../../../../core/data/dspace-object-data.service';
import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service'; import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '../../../../core/data/feature-authorization/feature-id'; import { FeatureID } from '../../../../core/data/feature-authorization/feature-id';
import { PaginatedList } from '../../../../core/data/paginated-list'; import { PaginatedList } from '../../../../core/data/paginated-list.model';
import { RemoteData } from '../../../../core/data/remote-data'; import { RemoteData } from '../../../../core/data/remote-data';
import { RequestService } from '../../../../core/data/request.service'; import { RequestService } from '../../../../core/data/request.service';
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
@@ -26,13 +31,19 @@ import { Group } from '../../../../core/eperson/models/group.model';
import { Collection } from '../../../../core/shared/collection.model'; import { Collection } from '../../../../core/shared/collection.model';
import { Community } from '../../../../core/shared/community.model'; import { Community } from '../../../../core/shared/community.model';
import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators'; import {
getRemoteDataPayload,
getFirstSucceededRemoteData,
getFirstCompletedRemoteData
} from '../../../../core/shared/operators';
import { AlertType } from '../../../../shared/alert/aletr-type'; import { AlertType } from '../../../../shared/alert/aletr-type';
import { ConfirmationModalComponent } from '../../../../shared/confirmation-modal/confirmation-modal.component'; import { ConfirmationModalComponent } from '../../../../shared/confirmation-modal/confirmation-modal.component';
import { hasValue, isNotEmpty } from '../../../../shared/empty.util'; import { hasValue, isNotEmpty, hasValueOperator } from '../../../../shared/empty.util';
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
import { NotificationsService } from '../../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { followLink } from '../../../../shared/utils/follow-link-config.model'; import { followLink } from '../../../../shared/utils/follow-link-config.model';
import { NoContent } from '../../../../core/shared/NoContent.model';
import { Operation } from 'fast-json-patch';
@Component({ @Component({
selector: 'ds-group-form', selector: 'ds-group-form',
@@ -135,6 +146,7 @@ export class GroupFormComponent implements OnInit, OnDestroy {
this.setActiveGroup(params.groupId) this.setActiveGroup(params.groupId)
})); }));
this.canEdit$ = this.groupDataService.getActiveGroup().pipe( this.canEdit$ = this.groupDataService.getActiveGroup().pipe(
hasValueOperator(),
switchMap((group: Group) => { switchMap((group: Group) => {
return observableCombineLatest( return observableCombineLatest(
this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(group) ? group.self : undefined), this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(group) ? group.self : undefined),
@@ -231,17 +243,17 @@ export class GroupFormComponent implements OnInit, OnDestroy {
*/ */
createNewGroup(values) { createNewGroup(values) {
const groupToCreate = Object.assign(new Group(), values); const groupToCreate = Object.assign(new Group(), values);
const response = this.groupDataService.tryToCreate(groupToCreate); this.groupDataService.create(groupToCreate).pipe(
response.pipe(take(1)).subscribe((restResponse: RestResponse) => { getFirstCompletedRemoteData()
if (restResponse.isSuccessful) { ).subscribe((rd: RemoteData<Group>) => {
if (rd.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.created.success', { name: groupToCreate.name })); this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.created.success', { name: groupToCreate.name }));
this.submitForm.emit(groupToCreate); this.submitForm.emit(groupToCreate);
const resp: any = restResponse; if (isNotEmpty(rd.payload)) {
if (isNotEmpty(resp.resourceSelfLinks)) { const groupSelfLink = rd.payload._links.self.href;
const groupSelfLink = resp.resourceSelfLinks[0];
this.setActiveGroupWithLink(groupSelfLink); this.setActiveGroupWithLink(groupSelfLink);
this.groupDataService.clearGroupsRequests(); this.groupDataService.clearGroupsRequests();
this.router.navigateByUrl(this.groupDataService.getGroupEditPageRouterLinkWithID(this.groupDataService.getUUIDFromString(groupSelfLink))); this.router.navigateByUrl(this.groupDataService.getGroupEditPageRouterLinkWithID(rd.payload.uuid));
} }
} else { } else {
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.created.failure', { name: groupToCreate.name })); this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.created.failure', { name: groupToCreate.name }));
@@ -262,7 +274,7 @@ export class GroupFormComponent implements OnInit, OnDestroy {
this.subs.push(this.groupDataService.searchGroups(group.name, { this.subs.push(this.groupDataService.searchGroups(group.name, {
currentPage: 1, currentPage: 1,
elementsPerPage: 0 elementsPerPage: 0
}).pipe(getSucceededRemoteData(), getRemoteDataPayload()) }).pipe(getFirstSucceededRemoteData(), getRemoteDataPayload())
.subscribe((list: PaginatedList<Group>) => { .subscribe((list: PaginatedList<Group>) => {
if (list.totalElements > 0) { if (list.totalElements > 0) {
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.' + notificationSection + '.failure.groupNameInUse', { this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.' + notificationSection + '.failure.groupNameInUse', {
@@ -277,25 +289,32 @@ export class GroupFormComponent implements OnInit, OnDestroy {
* @param group Group to edit and old values contained within * @param group Group to edit and old values contained within
*/ */
editGroup(group: Group) { editGroup(group: Group) {
const editedGroup = Object.assign(new Group(), { let operations: Operation[] = []
id: group.id,
metadata: { if (hasValue(this.groupDescription.value)) {
'dc.description': [ operations = [...operations, {
{ op: 'replace',
value: (hasValue(this.groupDescription.value) ? this.groupDescription.value : group.firstMetadataValue('dc.description')) path: '/metadata/dc.description/0/value',
} value: this.groupDescription.value
], }];
}, }
name: (hasValue(this.groupName.value) ? this.groupName.value : group.name),
_links: group._links, if (hasValue(this.groupName.value)) {
}); operations = [...operations, {
const response = this.groupDataService.updateGroup(editedGroup); op: 'replace',
response.pipe(take(1)).subscribe((restResponse: RestResponse) => { path: '/name',
if (restResponse.isSuccessful) { value: this.groupName.value
this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.edited.success', { name: editedGroup.name })); }];
this.submitForm.emit(editedGroup); }
this.groupDataService.patch(group, operations).pipe(
getFirstCompletedRemoteData()
).subscribe((rd: RemoteData<Group>) => {
if (rd.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.edited.success', { name: rd.payload.name }));
this.submitForm.emit(rd.payload);
} else { } else {
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.edited.failure', { name: editedGroup.name })); this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.edited.failure', { name: group.name }));
this.cancelForm.emit(); this.cancelForm.emit();
} }
}); });
@@ -309,7 +328,7 @@ export class GroupFormComponent implements OnInit, OnDestroy {
this.groupDataService.cancelEditGroup(); this.groupDataService.cancelEditGroup();
this.groupDataService.findById(groupId) this.groupDataService.findById(groupId)
.pipe( .pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload()) getRemoteDataPayload())
.subscribe((group: Group) => { .subscribe((group: Group) => {
this.groupDataService.editGroup(group); this.groupDataService.editGroup(group);
@@ -324,9 +343,9 @@ export class GroupFormComponent implements OnInit, OnDestroy {
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => { this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => {
if (activeGroup === null) { if (activeGroup === null) {
this.groupDataService.cancelEditGroup(); this.groupDataService.cancelEditGroup();
this.groupDataService.findByHref(groupSelfLink, followLink('subgroups'), followLink('epersons'), followLink('object')) this.groupDataService.findByHref(groupSelfLink, false, followLink('subgroups'), followLink('epersons'), followLink('object'))
.pipe( .pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload()) getRemoteDataPayload())
.subscribe((group: Group) => { .subscribe((group: Group) => {
this.groupDataService.editGroup(group); this.groupDataService.editGroup(group);
@@ -350,15 +369,15 @@ export class GroupFormComponent implements OnInit, OnDestroy {
modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => { modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => {
if (confirm) { if (confirm) {
if (hasValue(group.id)) { if (hasValue(group.id)) {
this.groupDataService.deleteGroup(group).pipe(take(1)) this.groupDataService.delete(group.id).pipe(getFirstCompletedRemoteData())
.subscribe(([success, optionalErrorMessage]: [boolean, string]) => { .subscribe((rd: RemoteData<NoContent>) => {
if (success) { if (rd.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.deleted.success', { name: group.name })); this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.deleted.success', { name: group.name }));
this.reset(); this.reset();
} else { } else {
this.notificationsService.error( this.notificationsService.error(
this.translateService.get(this.messagePrefix + '.notification.deleted.failure.title', { name: group.name }), this.translateService.get(this.messagePrefix + '.notification.deleted.failure.title', { name: group.name }),
this.translateService.get(this.messagePrefix + '.notification.deleted.failure.content', { cause: optionalErrorMessage })); this.translateService.get(this.messagePrefix + '.notification.deleted.failure.content', { cause: rd.errorMessage }));
} }
}) })
} }

View File

@@ -24,7 +24,7 @@
</div> </div>
</form> </form>
<ds-pagination *ngIf="(ePeopleSearch | async)?.payload.totalElements > 0" <ds-pagination *ngIf="(ePeopleSearch | async)?.payload?.totalElements > 0"
[paginationOptions]="configSearch" [paginationOptions]="configSearch"
[pageInfoState]="(ePeopleSearch | async)?.payload" [pageInfoState]="(ePeopleSearch | async)?.payload"
[collectionSize]="(ePeopleSearch | async)?.payload?.totalElements" [collectionSize]="(ePeopleSearch | async)?.payload?.totalElements"
@@ -70,7 +70,7 @@
</ds-pagination> </ds-pagination>
<div *ngIf="(ePeopleSearch | async)?.payload.totalElements == 0 && searchDone" <div *ngIf="(ePeopleSearch | async)?.payload?.totalElements == 0 && searchDone"
class="alert alert-info w-100 mb-2" class="alert alert-info w-100 mb-2"
role="alert"> role="alert">
{{messagePrefix + '.no-items' | translate}} {{messagePrefix + '.no-items' | translate}}
@@ -78,7 +78,7 @@
<h4>{{messagePrefix + '.headMembers' | translate}}</h4> <h4>{{messagePrefix + '.headMembers' | translate}}</h4>
<ds-pagination *ngIf="(ePeopleMembersOfGroup | async)?.payload.totalElements > 0" <ds-pagination *ngIf="(ePeopleMembersOfGroup | async)?.payload?.totalElements > 0"
[paginationOptions]="config" [paginationOptions]="config"
[pageInfoState]="(ePeopleMembersOfGroup | async)?.payload" [pageInfoState]="(ePeopleMembersOfGroup | async)?.payload"
[collectionSize]="(ePeopleMembersOfGroup | async)?.payload?.totalElements" [collectionSize]="(ePeopleMembersOfGroup | async)?.payload?.totalElements"
@@ -116,7 +116,7 @@
</ds-pagination> </ds-pagination>
<div *ngIf="(ePeopleMembersOfGroup | async)?.payload.totalElements == 0" class="alert alert-info w-100 mb-2" <div *ngIf="(ePeopleMembersOfGroup | async)?.payload?.totalElements == 0" class="alert alert-info w-100 mb-2"
role="alert"> role="alert">
{{messagePrefix + '.no-members-yet' | translate}} {{messagePrefix + '.no-members-yet' | translate}}
</div> </div>

View File

@@ -1,6 +1,14 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NO_ERRORS_SCHEMA } from '@angular/core'; import { NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, fakeAsync, flush, inject, TestBed, tick } from '@angular/core/testing'; import {
async,
ComponentFixture,
fakeAsync,
flush,
inject,
TestBed,
tick
} from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule, By } from '@angular/platform-browser'; import { BrowserModule, By } from '@angular/platform-browser';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
@@ -8,7 +16,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { RestResponse } from '../../../../../core/cache/response.models'; import { RestResponse } from '../../../../../core/cache/response.models';
import { PaginatedList } from '../../../../../core/data/paginated-list'; import { PaginatedList, buildPaginatedList } from '../../../../../core/data/paginated-list.model';
import { RemoteData } from '../../../../../core/data/remote-data'; import { RemoteData } from '../../../../../core/data/remote-data';
import { EPersonDataService } from '../../../../../core/eperson/eperson-data.service'; import { EPersonDataService } from '../../../../../core/eperson/eperson-data.service';
import { GroupDataService } from '../../../../../core/eperson/group-data.service'; import { GroupDataService } from '../../../../../core/eperson/group-data.service';
@@ -52,13 +60,13 @@ describe('MembersListComponent', () => {
epersonMembers: epersonMembers, epersonMembers: epersonMembers,
subgroupMembers: subgroupMembers, subgroupMembers: subgroupMembers,
findAllByHref(href: string): Observable<RemoteData<PaginatedList<EPerson>>> { findAllByHref(href: string): Observable<RemoteData<PaginatedList<EPerson>>> {
return createSuccessfulRemoteDataObject$(new PaginatedList<EPerson>(new PageInfo(), groupsDataServiceStub.getEPersonMembers())) return createSuccessfulRemoteDataObject$(buildPaginatedList<EPerson>(new PageInfo(), groupsDataServiceStub.getEPersonMembers()))
}, },
searchByScope(scope: string, query: string): Observable<RemoteData<PaginatedList<EPerson>>> { searchByScope(scope: string, query: string): Observable<RemoteData<PaginatedList<EPerson>>> {
if (query === '') { if (query === '') {
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), allEPersons)) return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), allEPersons))
} }
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])) return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []))
}, },
clearEPersonRequests() { clearEPersonRequests() {
// empty // empty
@@ -83,9 +91,9 @@ describe('MembersListComponent', () => {
}, },
searchGroups(query: string): Observable<RemoteData<PaginatedList<Group>>> { searchGroups(query: string): Observable<RemoteData<PaginatedList<Group>>> {
if (query === '') { if (query === '') {
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), this.allGroups)) return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), this.allGroups))
} }
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])) return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []))
}, },
addMemberToGroup(parentGroup, eperson: EPerson): Observable<RestResponse> { addMemberToGroup(parentGroup, eperson: EPerson): Observable<RestResponse> {
this.epersonMembers = [...this.epersonMembers, eperson]; this.epersonMembers = [...this.epersonMembers, eperson];

View File

@@ -4,14 +4,17 @@ import { Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Observable, of as observableOf, Subscription } from 'rxjs'; import { Observable, of as observableOf, Subscription } from 'rxjs';
import { map, mergeMap, take } from 'rxjs/operators'; import { map, mergeMap, take } from 'rxjs/operators';
import { RestResponse } from '../../../../../core/cache/response.models'; import { PaginatedList } from '../../../../../core/data/paginated-list.model';
import { PaginatedList } from '../../../../../core/data/paginated-list';
import { RemoteData } from '../../../../../core/data/remote-data'; import { RemoteData } from '../../../../../core/data/remote-data';
import { EPersonDataService } from '../../../../../core/eperson/eperson-data.service'; import { EPersonDataService } from '../../../../../core/eperson/eperson-data.service';
import { GroupDataService } from '../../../../../core/eperson/group-data.service'; import { GroupDataService } from '../../../../../core/eperson/group-data.service';
import { EPerson } from '../../../../../core/eperson/models/eperson.model'; import { EPerson } from '../../../../../core/eperson/models/eperson.model';
import { Group } from '../../../../../core/eperson/models/group.model'; import { Group } from '../../../../../core/eperson/models/group.model';
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../../core/shared/operators'; import {
getRemoteDataPayload,
getFirstSucceededRemoteData,
getFirstCompletedRemoteData
} from '../../../../../core/shared/operators';
import { hasValue } from '../../../../../shared/empty.util'; import { hasValue } from '../../../../../shared/empty.util';
import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../../../shared/notifications/notifications.service';
import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model';
@@ -160,7 +163,7 @@ export class MembersListComponent implements OnInit, OnDestroy {
elementsPerPage: Number.MAX_SAFE_INTEGER elementsPerPage: Number.MAX_SAFE_INTEGER
}) })
.pipe( .pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
map((listEPeopleInGroup: PaginatedList<EPerson>) => listEPeopleInGroup.page.filter((ePersonInList: EPerson) => ePersonInList.id === possibleMember.id)), map((listEPeopleInGroup: PaginatedList<EPerson>) => listEPeopleInGroup.page.filter((ePersonInList: EPerson) => ePersonInList.id === possibleMember.id)),
map((epeople: EPerson[]) => epeople.length > 0)) map((epeople: EPerson[]) => epeople.length > 0))
@@ -225,9 +228,9 @@ export class MembersListComponent implements OnInit, OnDestroy {
* @param nameObject Object request was about * @param nameObject Object request was about
* @param activeGroup Group currently being edited * @param activeGroup Group currently being edited
*/ */
showNotifications(messageSuffix: string, response: Observable<RestResponse>, nameObject: string, activeGroup: Group) { showNotifications(messageSuffix: string, response: Observable<RemoteData<any>>, nameObject: string, activeGroup: Group) {
response.pipe(take(1)).subscribe((restResponse: RestResponse) => { response.pipe(getFirstCompletedRemoteData()).subscribe((rd: RemoteData<any>) => {
if (restResponse.isSuccessful) { if (rd.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.success.' + messageSuffix, { name: nameObject })); this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.success.' + messageSuffix, { name: nameObject }));
} else { } else {
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.' + messageSuffix, { name: nameObject })); this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.' + messageSuffix, { name: nameObject }));

View File

@@ -18,7 +18,7 @@
</div> </div>
</form> </form>
<ds-pagination *ngIf="(groupsSearch | async)?.payload.totalElements > 0" <ds-pagination *ngIf="(groupsSearch | async)?.payload?.totalElements > 0"
[paginationOptions]="configSearch" [paginationOptions]="configSearch"
[pageInfoState]="(groupsSearch | async)?.payload" [pageInfoState]="(groupsSearch | async)?.payload"
[collectionSize]="(groupsSearch | async)?.payload?.totalElements" [collectionSize]="(groupsSearch | async)?.payload?.totalElements"
@@ -65,14 +65,14 @@
</div> </div>
</ds-pagination> </ds-pagination>
<div *ngIf="(groupsSearch | async)?.payload.totalElements == 0 && searchDone" class="alert alert-info w-100 mb-2" <div *ngIf="(groupsSearch | async)?.payload?.totalElements == 0 && searchDone" class="alert alert-info w-100 mb-2"
role="alert"> role="alert">
{{messagePrefix + '.no-items' | translate}} {{messagePrefix + '.no-items' | translate}}
</div> </div>
<h4>{{messagePrefix + '.headSubgroups' | translate}}</h4> <h4>{{messagePrefix + '.headSubgroups' | translate}}</h4>
<ds-pagination *ngIf="(subgroupsOfGroup | async)?.payload.totalElements > 0" <ds-pagination *ngIf="(subgroupsOfGroup | async)?.payload?.totalElements > 0"
[paginationOptions]="config" [paginationOptions]="config"
[pageInfoState]="(subgroupsOfGroup | async)?.payload" [pageInfoState]="(subgroupsOfGroup | async)?.payload"
[collectionSize]="(subgroupsOfGroup | async)?.payload?.totalElements" [collectionSize]="(subgroupsOfGroup | async)?.payload?.totalElements"
@@ -109,7 +109,7 @@
</div> </div>
</ds-pagination> </ds-pagination>
<div *ngIf="(subgroupsOfGroup | async)?.payload.totalElements == 0" class="alert alert-info w-100 mb-2" <div *ngIf="(subgroupsOfGroup | async)?.payload?.totalElements == 0" class="alert alert-info w-100 mb-2"
role="alert"> role="alert">
{{messagePrefix + '.no-subgroups-yet' | translate}} {{messagePrefix + '.no-subgroups-yet' | translate}}
</div> </div>

View File

@@ -8,7 +8,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { RestResponse } from '../../../../../core/cache/response.models'; import { RestResponse } from '../../../../../core/cache/response.models';
import { PaginatedList } from '../../../../../core/data/paginated-list'; import { PaginatedList, buildPaginatedList } from '../../../../../core/data/paginated-list.model';
import { RemoteData } from '../../../../../core/data/remote-data'; import { RemoteData } from '../../../../../core/data/remote-data';
import { GroupDataService } from '../../../../../core/eperson/group-data.service'; import { GroupDataService } from '../../../../../core/eperson/group-data.service';
import { Group } from '../../../../../core/eperson/models/group.model'; import { Group } from '../../../../../core/eperson/models/group.model';
@@ -52,16 +52,16 @@ describe('SubgroupsListComponent', () => {
return this.activeGroup; return this.activeGroup;
}, },
findAllByHref(href: string): Observable<RemoteData<PaginatedList<Group>>> { findAllByHref(href: string): Observable<RemoteData<PaginatedList<Group>>> {
return createSuccessfulRemoteDataObject$(new PaginatedList<Group>(new PageInfo(), this.subgroups)) return createSuccessfulRemoteDataObject$(buildPaginatedList<Group>(new PageInfo(), this.subgroups))
}, },
getGroupEditPageRouterLink(group: Group): string { getGroupEditPageRouterLink(group: Group): string {
return '/admin/access-control/groups/' + group.id; return '/admin/access-control/groups/' + group.id;
}, },
searchGroups(query: string): Observable<RemoteData<PaginatedList<Group>>> { searchGroups(query: string): Observable<RemoteData<PaginatedList<Group>>> {
if (query === '') { if (query === '') {
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), allGroups)) return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), allGroups))
} }
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])) return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []))
}, },
addSubGroupToGroup(parentGroup, subgroup: Group): Observable<RestResponse> { addSubGroupToGroup(parentGroup, subgroup: Group): Observable<RestResponse> {
this.subgroups = [...this.subgroups, subgroup]; this.subgroups = [...this.subgroups, subgroup];

View File

@@ -4,12 +4,15 @@ import { Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Observable, of as observableOf, Subscription } from 'rxjs'; import { Observable, of as observableOf, Subscription } from 'rxjs';
import { map, mergeMap, take } from 'rxjs/operators'; import { map, mergeMap, take } from 'rxjs/operators';
import { RestResponse } from '../../../../../core/cache/response.models'; import { PaginatedList } from '../../../../../core/data/paginated-list.model';
import { PaginatedList } from '../../../../../core/data/paginated-list';
import { RemoteData } from '../../../../../core/data/remote-data'; import { RemoteData } from '../../../../../core/data/remote-data';
import { GroupDataService } from '../../../../../core/eperson/group-data.service'; import { GroupDataService } from '../../../../../core/eperson/group-data.service';
import { Group } from '../../../../../core/eperson/models/group.model'; import { Group } from '../../../../../core/eperson/models/group.model';
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../../core/shared/operators'; import {
getRemoteDataPayload,
getFirstSucceededRemoteData,
getFirstCompletedRemoteData
} from '../../../../../core/shared/operators';
import { hasValue } from '../../../../../shared/empty.util'; import { hasValue } from '../../../../../shared/empty.util';
import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../../../shared/notifications/notifications.service';
import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model';
@@ -125,7 +128,7 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
elementsPerPage: Number.MAX_SAFE_INTEGER elementsPerPage: Number.MAX_SAFE_INTEGER
}) })
.pipe( .pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
map((listTotalGroups: PaginatedList<Group>) => listTotalGroups.page.filter((groupInList: Group) => groupInList.id === possibleSubgroup.id)), map((listTotalGroups: PaginatedList<Group>) => listTotalGroups.page.filter((groupInList: Group) => groupInList.id === possibleSubgroup.id)),
map((groups: Group[]) => groups.length > 0)) map((groups: Group[]) => groups.length > 0))
@@ -231,9 +234,9 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
* @param nameObject Object request was about * @param nameObject Object request was about
* @param activeGroup Group currently being edited * @param activeGroup Group currently being edited
*/ */
showNotifications(messageSuffix: string, response: Observable<RestResponse>, nameObject: string, activeGroup: Group) { showNotifications(messageSuffix: string, response: Observable<RemoteData<Group>>, nameObject: string, activeGroup: Group) {
response.pipe(take(1)).subscribe((restResponse: RestResponse) => { response.pipe(getFirstCompletedRemoteData()).subscribe((rd: RemoteData<Group>) => {
if (restResponse.isSuccessful) { if (rd.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.success.' + messageSuffix, { name: nameObject })); this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.success.' + messageSuffix, { name: nameObject }));
} else { } else {
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.' + messageSuffix, { name: nameObject })); this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.' + messageSuffix, { name: nameObject }));

View File

@@ -9,7 +9,7 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { Observable, of as observableOf } from 'rxjs'; import { Observable, of as observableOf } from 'rxjs';
import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service'; import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service';
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginatedList, buildPaginatedList } from '../../../core/data/paginated-list.model';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { RequestService } from '../../../core/data/request.service'; import { RequestService } from '../../../core/data/request.service';
import { EPersonDataService } from '../../../core/eperson/eperson-data.service'; import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
@@ -47,11 +47,11 @@ describe('GroupRegistryComponent', () => {
findAllByHref(href: string): Observable<RemoteData<PaginatedList<EPerson>>> { findAllByHref(href: string): Observable<RemoteData<PaginatedList<EPerson>>> {
switch (href) { switch (href) {
case 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/epersons': case 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/epersons':
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: 1, totalElements: 0, totalPages: 0, currentPage: 1 }), [])); return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo({ elementsPerPage: 1, totalElements: 0, totalPages: 0, currentPage: 1 }), []));
case 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/epersons': case 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/epersons':
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: 1, totalElements: 1, totalPages: 1, currentPage: 1 }), [EPersonMock])); return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo({ elementsPerPage: 1, totalElements: 1, totalPages: 1, currentPage: 1 }), [EPersonMock]));
default: default:
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: 1, totalElements: 0, totalPages: 0, currentPage: 1 }), [])); return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo({ elementsPerPage: 1, totalElements: 0, totalPages: 0, currentPage: 1 }), []));
} }
} }
}; };
@@ -60,11 +60,11 @@ describe('GroupRegistryComponent', () => {
findAllByHref(href: string): Observable<RemoteData<PaginatedList<Group>>> { findAllByHref(href: string): Observable<RemoteData<PaginatedList<Group>>> {
switch (href) { switch (href) {
case 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/groups': case 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/groups':
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: 1, totalElements: 0, totalPages: 0, currentPage: 1 }), [])); return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo({ elementsPerPage: 1, totalElements: 0, totalPages: 0, currentPage: 1 }), []));
case 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/groups': case 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/groups':
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: 1, totalElements: 1, totalPages: 1, currentPage: 1 }), [GroupMock2])); return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo({ elementsPerPage: 1, totalElements: 1, totalPages: 1, currentPage: 1 }), [GroupMock2]));
default: default:
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: 1, totalElements: 0, totalPages: 0, currentPage: 1 }), [])); return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo({ elementsPerPage: 1, totalElements: 0, totalPages: 0, currentPage: 1 }), []));
} }
}, },
getGroupEditPageRouterLink(group: Group): string { getGroupEditPageRouterLink(group: Group): string {
@@ -75,12 +75,12 @@ describe('GroupRegistryComponent', () => {
}, },
searchGroups(query: string): Observable<RemoteData<PaginatedList<Group>>> { searchGroups(query: string): Observable<RemoteData<PaginatedList<Group>>> {
if (query === '') { if (query === '') {
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: this.allGroups.length, totalElements: this.allGroups.length, totalPages: 1, currentPage: 1 }), this.allGroups)); return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo({ elementsPerPage: this.allGroups.length, totalElements: this.allGroups.length, totalPages: 1, currentPage: 1 }), this.allGroups));
} }
const result = this.allGroups.find((group: Group) => { const result = this.allGroups.find((group: Group) => {
return (group.id.includes(query)) return (group.id.includes(query))
}); });
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo({ elementsPerPage: [result].length, totalElements: [result].length, totalPages: 1, currentPage: 1 }), [result])); return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo({ elementsPerPage: [result].length, totalElements: [result].length, totalPages: 1, currentPage: 1 }), [result]));
} }
}; };
dsoDataServiceStub = { dsoDataServiceStub = {

View File

@@ -2,14 +2,20 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms'; import { FormBuilder } from '@angular/forms';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, combineLatest as observableCombineLatest, Subscription, Observable, of as observableOf } from 'rxjs'; import {
BehaviorSubject,
combineLatest as observableCombineLatest,
Subscription,
Observable,
of as observableOf
} from 'rxjs';
import { filter } from 'rxjs/internal/operators/filter'; import { filter } from 'rxjs/internal/operators/filter';
import { ObservedValueOf } from 'rxjs/internal/types'; import { ObservedValueOf } from 'rxjs/internal/types';
import { catchError, map, switchMap, take } from 'rxjs/operators'; import { catchError, map, switchMap, take } from 'rxjs/operators';
import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service'; import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service';
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginatedList, buildPaginatedList } from '../../../core/data/paginated-list.model';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { RequestService } from '../../../core/data/request.service'; import { RequestService } from '../../../core/data/request.service';
import { EPersonDataService } from '../../../core/eperson/eperson-data.service'; import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
@@ -19,11 +25,15 @@ import { GroupDtoModel } from '../../../core/eperson/models/group-dto.model';
import { Group } from '../../../core/eperson/models/group.model'; import { Group } from '../../../core/eperson/models/group.model';
import { RouteService } from '../../../core/services/route.service'; import { RouteService } from '../../../core/services/route.service';
import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { getAllSucceededRemoteDataPayload } from '../../../core/shared/operators'; import {
getAllSucceededRemoteDataPayload,
getFirstCompletedRemoteData
} from '../../../core/shared/operators';
import { PageInfo } from '../../../core/shared/page-info.model'; import { PageInfo } from '../../../core/shared/page-info.model';
import { hasValue } from '../../../shared/empty.util'; import { hasValue } from '../../../shared/empty.util';
import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { NoContent } from '../../../core/shared/NoContent.model';
@Component({ @Component({
selector: 'ds-groups-registry', selector: 'ds-groups-registry',
@@ -115,7 +125,8 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
this.subs.push(this.groupService.searchGroups(this.currentSearchQuery.trim(), { this.subs.push(this.groupService.searchGroups(this.currentSearchQuery.trim(), {
currentPage: this.config.currentPage, currentPage: this.config.currentPage,
elementsPerPage: this.config.pageSize elementsPerPage: this.config.pageSize
}).subscribe((groupsRD: RemoteData<PaginatedList<Group>>) => { }).pipe(getFirstCompletedRemoteData())
.subscribe((groupsRD: RemoteData<PaginatedList<Group>>) => {
this.groups$.next(groupsRD); this.groups$.next(groupsRD);
this.pageInfoState$.next(groupsRD.payload.pageInfo); this.pageInfoState$.next(groupsRD.payload.pageInfo);
} }
@@ -136,7 +147,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
} }
) )
})).pipe(map((dtos: GroupDtoModel[]) => { })).pipe(map((dtos: GroupDtoModel[]) => {
return new PaginatedList(groups.pageInfo, dtos); return buildPaginatedList(groups.pageInfo, dtos);
})) }))
})).subscribe((value: PaginatedList<GroupDtoModel>) => { })).subscribe((value: PaginatedList<GroupDtoModel>) => {
this.groupsDto$.next(value); this.groupsDto$.next(value);
@@ -149,15 +160,15 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
*/ */
deleteGroup(group: Group) { deleteGroup(group: Group) {
if (hasValue(group.id)) { if (hasValue(group.id)) {
this.groupService.deleteGroup(group).pipe(take(1)) this.groupService.delete(group.id).pipe(getFirstCompletedRemoteData())
.subscribe(([success, optionalErrorMessage]: [boolean, string]) => { .subscribe((rd: RemoteData<NoContent>) => {
if (success) { if (rd.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.messagePrefix + 'notification.deleted.success', { name: group.name })); this.notificationsService.success(this.translateService.get(this.messagePrefix + 'notification.deleted.success', { name: group.name }));
this.reset(); this.reset();
} else { } else {
this.notificationsService.error( this.notificationsService.error(
this.translateService.get(this.messagePrefix + 'notification.deleted.failure.title', { name: group.name }), this.translateService.get(this.messagePrefix + 'notification.deleted.failure.title', { name: group.name }),
this.translateService.get(this.messagePrefix + 'notification.deleted.failure.content', { cause: optionalErrorMessage })); this.translateService.get(this.messagePrefix + 'notification.deleted.failure.content', { cause: rd.errorMessage }));
} }
}) })
} }

View File

@@ -19,6 +19,7 @@ import { NotificationsServiceStub } from '../../shared/testing/notifications-ser
import { FileValueAccessorDirective } from '../../shared/utils/file-value-accessor.directive'; import { FileValueAccessorDirective } from '../../shared/utils/file-value-accessor.directive';
import { FileValidator } from '../../shared/utils/require-file.validator'; import { FileValidator } from '../../shared/utils/require-file.validator';
import { MetadataImportPageComponent } from './metadata-import-page.component'; import { MetadataImportPageComponent } from './metadata-import-page.component';
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
describe('MetadataImportPageComponent', () => { describe('MetadataImportPageComponent', () => {
let comp: MetadataImportPageComponent; let comp: MetadataImportPageComponent;
@@ -36,13 +37,7 @@ describe('MetadataImportPageComponent', () => {
notificationService = new NotificationsServiceStub(); notificationService = new NotificationsServiceStub();
scriptService = jasmine.createSpyObj('scriptService', scriptService = jasmine.createSpyObj('scriptService',
{ {
invoke: observableOf({ invoke: createSuccessfulRemoteDataObject$({ processId: '45' })
response:
{
isSuccessful: true,
resourceSelfLinks: ['https://localhost:8080/api/core/processes/45']
}
})
} }
); );
user = Object.assign(new EPerson(), { user = Object.assign(new EPerson(), {
@@ -133,12 +128,7 @@ describe('MetadataImportPageComponent', () => {
describe('if proceed is pressed; but script invoke fails', () => { describe('if proceed is pressed; but script invoke fails', () => {
beforeEach(fakeAsync(() => { beforeEach(fakeAsync(() => {
jasmine.getEnv().allowRespy(true); jasmine.getEnv().allowRespy(true);
spyOn(scriptService, 'invoke').and.returnValue(observableOf({ spyOn(scriptService, 'invoke').and.returnValue(createFailedRemoteDataObject$('Error', 500));
response:
{
isSuccessful: false,
}
}));
const proceed = fixture.debugElement.query(By.css('#proceedButton')).nativeElement; const proceed = fixture.debugElement.query(By.css('#proceedButton')).nativeElement;
proceed.click(); proceed.click();
fixture.detectChanges(); fixture.detectChanges();

View File

@@ -3,14 +3,20 @@ import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { map, switchMap, take } from 'rxjs/operators'; import { map, switchMap } from 'rxjs/operators';
import { AuthService } from '../../core/auth/auth.service'; import { AuthService } from '../../core/auth/auth.service';
import { METADATA_IMPORT_SCRIPT_NAME, ScriptDataService } from '../../core/data/processes/script-data.service'; import {
import { RequestEntry } from '../../core/data/request.reducer'; METADATA_IMPORT_SCRIPT_NAME,
ScriptDataService
} from '../../core/data/processes/script-data.service';
import { EPerson } from '../../core/eperson/models/eperson.model'; import { EPerson } from '../../core/eperson/models/eperson.model';
import { ProcessParameter } from '../../process-page/processes/process-parameter.model'; import { ProcessParameter } from '../../process-page/processes/process-parameter.model';
import { isNotEmpty } from '../../shared/empty.util'; import { isNotEmpty } from '../../shared/empty.util';
import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NotificationsService } from '../../shared/notifications/notifications.service';
import { RemoteData } from '../../core/data/remote-data';
import { Process } from '../../process-page/processes/process.model';
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
import { getProcessDetailRoute } from '../../process-page/process-page-routing.paths';
@Component({ @Component({
selector: 'ds-metadata-import-page', selector: 'ds-metadata-import-page',
@@ -79,28 +85,23 @@ export class MetadataImportPageComponent implements OnInit {
Object.assign(new ProcessParameter(), { name: '-f', value: this.fileObject.name }), Object.assign(new ProcessParameter(), { name: '-f', value: this.fileObject.name }),
]; ];
return this.scriptDataService.invoke(METADATA_IMPORT_SCRIPT_NAME, parameterValues, [this.fileObject]) return this.scriptDataService.invoke(METADATA_IMPORT_SCRIPT_NAME, parameterValues, [this.fileObject])
.pipe(
take(1),
map((requestEntry: RequestEntry) => {
if (requestEntry.response.isSuccessful) {
const title = this.translate.get('process.new.notification.success.title');
const content = this.translate.get('process.new.notification.success.content');
this.notificationsService.success(title, content);
const response: any = requestEntry.response;
if (isNotEmpty(response.resourceSelfLinks)) {
const processNumber = response.resourceSelfLinks[0].split('/').pop();
this.router.navigateByUrl('/processes/' + processNumber);
}
} else {
const title = this.translate.get('process.new.notification.error.title');
const content = this.translate.get('process.new.notification.error.content');
this.notificationsService.error(title, content);
}
}));
} }
}), }),
take(1) getFirstCompletedRemoteData(),
).subscribe(); ).subscribe((rd: RemoteData<Process>) => {
if (rd.hasSucceeded) {
const title = this.translate.get('process.new.notification.success.title');
const content = this.translate.get('process.new.notification.success.content');
this.notificationsService.success(title, content);
if (isNotEmpty(rd.payload)) {
this.router.navigateByUrl(getProcessDetailRoute(rd.payload.processId));
}
} else {
const title = this.translate.get('process.new.notification.error.title');
const content = this.translate.get('process.new.notification.error.content');
this.notificationsService.error(title, content);
}
});
} }
} }
} }

View File

@@ -6,7 +6,6 @@ import { RouterTestingModule } from '@angular/router/testing';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { RestResponse } from '../../../../core/cache/response.models';
import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service'; import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level'; import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level';
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model'; import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
@@ -14,6 +13,7 @@ import { NotificationsService } from '../../../../shared/notifications/notificat
import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub'; import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
import { RouterStub } from '../../../../shared/testing/router.stub'; import { RouterStub } from '../../../../shared/testing/router.stub';
import { AddBitstreamFormatComponent } from './add-bitstream-format.component'; import { AddBitstreamFormatComponent } from './add-bitstream-format.component';
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
describe('AddBitstreamFormatComponent', () => { describe('AddBitstreamFormatComponent', () => {
let comp: AddBitstreamFormatComponent; let comp: AddBitstreamFormatComponent;
@@ -37,7 +37,7 @@ describe('AddBitstreamFormatComponent', () => {
router = new RouterStub(); router = new RouterStub();
notificationService = new NotificationsServiceStub(); notificationService = new NotificationsServiceStub();
bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', { bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
createBitstreamFormat: observableOf(new RestResponse(true, 200, 'Success')), createBitstreamFormat: createSuccessfulRemoteDataObject$({}),
clearBitStreamFormatRequests: observableOf(null) clearBitStreamFormatRequests: observableOf(null)
}); });
@@ -77,7 +77,7 @@ describe('AddBitstreamFormatComponent', () => {
router = new RouterStub(); router = new RouterStub();
notificationService = new NotificationsServiceStub(); notificationService = new NotificationsServiceStub();
bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', { bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
createBitstreamFormat: observableOf(new RestResponse(false, 400, 'Bad Request')), createBitstreamFormat: createFailedRemoteDataObject$('Error', 500),
clearBitStreamFormatRequests: observableOf(null) clearBitStreamFormatRequests: observableOf(null)
}); });

View File

@@ -1,12 +1,12 @@
import { take } from 'rxjs/operators';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model'; import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service'; import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
import { RestResponse } from '../../../../core/cache/response.models';
import { NotificationsService } from '../../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { getBitstreamFormatsModuleRoute } from '../../admin-registries-routing-paths'; import { getBitstreamFormatsModuleRoute } from '../../admin-registries-routing-paths';
import { RemoteData } from '../../../../core/data/remote-data';
import { getFirstCompletedRemoteData } from '../../../../core/shared/operators';
/** /**
* This component renders the page to create a new bitstream format. * This component renders the page to create a new bitstream format.
@@ -32,9 +32,10 @@ export class AddBitstreamFormatComponent {
* @param bitstreamFormat * @param bitstreamFormat
*/ */
createBitstreamFormat(bitstreamFormat: BitstreamFormat) { createBitstreamFormat(bitstreamFormat: BitstreamFormat) {
this.bitstreamFormatDataService.createBitstreamFormat(bitstreamFormat).pipe(take(1) this.bitstreamFormatDataService.createBitstreamFormat(bitstreamFormat).pipe(
).subscribe((response: RestResponse) => { getFirstCompletedRemoteData(),
if (response.isSuccessful) { ).subscribe((response: RemoteData<BitstreamFormat>) => {
if (response.hasSucceeded) {
this.notificationService.success(this.translateService.get('admin.registries.bitstream-formats.create.success.head'), this.notificationService.success(this.translateService.get('admin.registries.bitstream-formats.create.success.head'),
this.translateService.get('admin.registries.bitstream-formats.create.success.content')); this.translateService.get('admin.registries.bitstream-formats.create.success.content'));
this.router.navigate([getBitstreamFormatsModuleRoute()]); this.router.navigate([getBitstreamFormatsModuleRoute()]);

View File

@@ -1,8 +1,6 @@
import { BitstreamFormatsComponent } from './bitstream-formats.component'; import { BitstreamFormatsComponent } from './bitstream-formats.component';
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
@@ -19,6 +17,12 @@ import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
import { BitstreamFormatSupportLevel } from '../../../core/shared/bitstream-format-support-level'; import { BitstreamFormatSupportLevel } from '../../../core/shared/bitstream-format-support-level';
import { cold, getTestScheduler, hot } from 'jasmine-marbles'; import { cold, getTestScheduler, hot } from 'jasmine-marbles';
import { TestScheduler } from 'rxjs/testing'; import { TestScheduler } from 'rxjs/testing';
import {
createNoContentRemoteDataObject$,
createSuccessfulRemoteDataObject,
createSuccessfulRemoteDataObject$
} from '../../../shared/remote-data.utils';
import { createPaginatedList } from '../../../shared/testing/utils.test';
describe('BitstreamFormatsComponent', () => { describe('BitstreamFormatsComponent', () => {
let comp: BitstreamFormatsComponent; let comp: BitstreamFormatsComponent;
@@ -73,7 +77,7 @@ describe('BitstreamFormatsComponent', () => {
bitstreamFormat3, bitstreamFormat3,
bitstreamFormat4 bitstreamFormat4
]; ];
const mockFormatsRD = new RemoteData(false, false, true, undefined, new PaginatedList(null, mockFormatsList)); const mockFormatsRD = createSuccessfulRemoteDataObject(createPaginatedList(mockFormatsList));
const initAsync = () => { const initAsync = () => {
notificationsServiceStub = new NotificationsServiceStub(); notificationsServiceStub = new NotificationsServiceStub();
@@ -82,12 +86,12 @@ describe('BitstreamFormatsComponent', () => {
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', { bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
findAll: observableOf(mockFormatsRD), findAll: observableOf(mockFormatsRD),
find: observableOf(new RemoteData(false, false, true, undefined, mockFormatsList[0])), find: createSuccessfulRemoteDataObject$(mockFormatsList[0]),
getSelectedBitstreamFormats: hot('a', {a: mockFormatsList}), getSelectedBitstreamFormats: hot('a', {a: mockFormatsList}),
selectBitstreamFormat: {}, selectBitstreamFormat: {},
deselectBitstreamFormat: {}, deselectBitstreamFormat: {},
deselectAllBitstreamFormats: {}, deselectAllBitstreamFormats: {},
delete: observableOf(true), delete: createSuccessfulRemoteDataObject$({}),
clearBitStreamFormatRequests: observableOf('cleared') clearBitStreamFormatRequests: observableOf('cleared')
}); });
@@ -204,12 +208,12 @@ describe('BitstreamFormatsComponent', () => {
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', { bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
findAll: observableOf(mockFormatsRD), findAll: observableOf(mockFormatsRD),
find: observableOf(new RemoteData(false, false, true, undefined, mockFormatsList[0])), find: createSuccessfulRemoteDataObject$(mockFormatsList[0]),
getSelectedBitstreamFormats: observableOf(mockFormatsList), getSelectedBitstreamFormats: observableOf(mockFormatsList),
selectBitstreamFormat: {}, selectBitstreamFormat: {},
deselectBitstreamFormat: {}, deselectBitstreamFormat: {},
deselectAllBitstreamFormats: {}, deselectAllBitstreamFormats: {},
delete: observableOf({ isSuccessful: true }), delete: createNoContentRemoteDataObject$(),
clearBitStreamFormatRequests: observableOf('cleared') clearBitStreamFormatRequests: observableOf('cleared')
}); });
@@ -250,7 +254,7 @@ describe('BitstreamFormatsComponent', () => {
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', { bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
findAll: observableOf(mockFormatsRD), findAll: observableOf(mockFormatsRD),
find: observableOf(new RemoteData(false, false, true, undefined, mockFormatsList[0])), find: createSuccessfulRemoteDataObject$(mockFormatsList[0]),
getSelectedBitstreamFormats: observableOf(mockFormatsList), getSelectedBitstreamFormats: observableOf(mockFormatsList),
selectBitstreamFormat: {}, selectBitstreamFormat: {},
deselectBitstreamFormat: {}, deselectBitstreamFormat: {},

View File

@@ -1,7 +1,7 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, zip } from 'rxjs'; import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, zip } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginatedList } from '../../../core/data/paginated-list.model';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model'; import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service'; import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
@@ -11,7 +11,7 @@ import { hasValue } from '../../../shared/empty.util';
import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { RestResponse } from '../../../core/cache/response.models'; import { NoContent } from '../../../core/shared/NoContent.model';
/** /**
* This component renders a list of bitstream formats * This component renders a list of bitstream formats
@@ -65,7 +65,7 @@ export class BitstreamFormatsComponent implements OnInit {
const tasks$ = []; const tasks$ = [];
for (const format of formats) { for (const format of formats) {
if (hasValue(format.id)) { if (hasValue(format.id)) {
tasks$.push(this.bitstreamFormatService.delete(format.id).pipe(map((response: RestResponse) => response.isSuccessful))); tasks$.push(this.bitstreamFormatService.delete(format.id).pipe(map((response: RemoteData<NoContent>) => response.hasSucceeded)));
} }
} }
zip(...tasks$).subscribe((results: boolean[]) => { zip(...tasks$).subscribe((results: boolean[]) => {

View File

@@ -1,11 +1,10 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { find } from 'rxjs/operators';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model'; import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service'; import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
import { hasValue } from '../../../shared/empty.util'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
/** /**
* This class represents a resolver that requests a specific bitstreamFormat before the route is activated * This class represents a resolver that requests a specific bitstreamFormat before the route is activated
@@ -25,7 +24,7 @@ export class BitstreamFormatsResolver implements Resolve<RemoteData<BitstreamFor
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<BitstreamFormat>> { resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<BitstreamFormat>> {
return this.bitstreamFormatDataService.findById(route.params.id) return this.bitstreamFormatDataService.findById(route.params.id)
.pipe( .pipe(
find((RD) => hasValue(RD.error) || RD.hasSucceeded), getFirstCompletedRemoteData()
); );
} }
} }

View File

@@ -6,7 +6,6 @@ import { RouterTestingModule } from '@angular/router/testing';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { RestResponse } from '../../../../core/cache/response.models';
import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service'; import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
import { RemoteData } from '../../../../core/data/remote-data'; import { RemoteData } from '../../../../core/data/remote-data';
import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level'; import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level';
@@ -15,6 +14,11 @@ import { NotificationsService } from '../../../../shared/notifications/notificat
import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub'; import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
import { RouterStub } from '../../../../shared/testing/router.stub'; import { RouterStub } from '../../../../shared/testing/router.stub';
import { EditBitstreamFormatComponent } from './edit-bitstream-format.component'; import { EditBitstreamFormatComponent } from './edit-bitstream-format.component';
import {
createFailedRemoteDataObject$,
createSuccessfulRemoteDataObject,
createSuccessfulRemoteDataObject$
} from '../../../../shared/remote-data.utils';
describe('EditBitstreamFormatComponent', () => { describe('EditBitstreamFormatComponent', () => {
let comp: EditBitstreamFormatComponent; let comp: EditBitstreamFormatComponent;
@@ -32,7 +36,7 @@ describe('EditBitstreamFormatComponent', () => {
const routeStub = { const routeStub = {
data: observableOf({ data: observableOf({
bitstreamFormat: new RemoteData(false, false, true, null, bitstreamFormat) bitstreamFormat: createSuccessfulRemoteDataObject(bitstreamFormat)
}) })
}; };
@@ -44,7 +48,7 @@ describe('EditBitstreamFormatComponent', () => {
router = new RouterStub(); router = new RouterStub();
notificationService = new NotificationsServiceStub(); notificationService = new NotificationsServiceStub();
bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', { bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
updateBitstreamFormat: observableOf(new RestResponse(true, 200, 'Success')) updateBitstreamFormat: createSuccessfulRemoteDataObject$({})
}); });
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -73,7 +77,8 @@ describe('EditBitstreamFormatComponent', () => {
it('should initialise the bitstreamFormat based on the route', () => { it('should initialise the bitstreamFormat based on the route', () => {
comp.bitstreamFormatRD$.subscribe((format: RemoteData<BitstreamFormat>) => { comp.bitstreamFormatRD$.subscribe((format: RemoteData<BitstreamFormat>) => {
expect(format).toEqual(new RemoteData(false, false, true, null, bitstreamFormat)); const expected = createSuccessfulRemoteDataObject(bitstreamFormat);
expect(format.payload).toEqual(expected.payload);
}); });
}); });
}); });
@@ -94,7 +99,7 @@ describe('EditBitstreamFormatComponent', () => {
router = new RouterStub(); router = new RouterStub();
notificationService = new NotificationsServiceStub(); notificationService = new NotificationsServiceStub();
bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', { bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
updateBitstreamFormat: observableOf(new RestResponse(false, 400, 'Bad Request')) updateBitstreamFormat: createFailedRemoteDataObject$('Error', 500)
}); });
TestBed.configureTestingModule({ TestBed.configureTestingModule({

View File

@@ -1,14 +1,14 @@
import { map, take } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { RemoteData } from '../../../../core/data/remote-data'; import { RemoteData } from '../../../../core/data/remote-data';
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model'; import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service'; import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
import { RestResponse } from '../../../../core/cache/response.models';
import { NotificationsService } from '../../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { getBitstreamFormatsModuleRoute } from '../../admin-registries-routing-paths'; import { getBitstreamFormatsModuleRoute } from '../../admin-registries-routing-paths';
import { getFirstCompletedRemoteData } from '../../../../core/shared/operators';
/** /**
* This component renders the edit page of a bitstream format. * This component renders the edit page of a bitstream format.
@@ -46,9 +46,10 @@ export class EditBitstreamFormatComponent implements OnInit {
* When failed, an error notification will be shown. * When failed, an error notification will be shown.
*/ */
updateFormat(bitstreamFormat: BitstreamFormat) { updateFormat(bitstreamFormat: BitstreamFormat) {
this.bitstreamFormatDataService.updateBitstreamFormat(bitstreamFormat).pipe(take(1) this.bitstreamFormatDataService.updateBitstreamFormat(bitstreamFormat).pipe(
).subscribe((response: RestResponse) => { getFirstCompletedRemoteData(),
if (response.isSuccessful) { ).subscribe((response: RemoteData<BitstreamFormat>) => {
if (response.hasSucceeded) {
this.notificationService.success(this.translateService.get('admin.registries.bitstream-formats.edit.success.head'), this.notificationService.success(this.translateService.get('admin.registries.bitstream-formats.edit.success.head'),
this.translateService.get('admin.registries.bitstream-formats.edit.success.content')); this.translateService.get('admin.registries.bitstream-formats.edit.success.content'));
this.router.navigate([getBitstreamFormatsModuleRoute()]); this.router.navigate([getBitstreamFormatsModuleRoute()]);

View File

@@ -1,7 +1,7 @@
import { MetadataRegistryComponent } from './metadata-registry.component'; import { MetadataRegistryComponent } from './metadata-registry.component';
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { PaginatedList } from '../../../core/data/paginated-list'; import { buildPaginatedList } from '../../../core/data/paginated-list.model';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
@@ -45,7 +45,7 @@ describe('MetadataRegistryComponent', () => {
namespace: 'http://dspace.org/mockschema' namespace: 'http://dspace.org/mockschema'
} }
]; ];
const mockSchemas = createSuccessfulRemoteDataObject$(new PaginatedList(null, mockSchemasList)); const mockSchemas = createSuccessfulRemoteDataObject$(buildPaginatedList(null, mockSchemasList));
/* tslint:disable:no-empty */ /* tslint:disable:no-empty */
const registryServiceStub = { const registryServiceStub = {
getMetadataSchemas: () => mockSchemas, getMetadataSchemas: () => mockSchemas,

View File

@@ -2,18 +2,19 @@ import { Component } from '@angular/core';
import { RegistryService } from '../../../core/registry/registry.service'; import { RegistryService } from '../../../core/registry/registry.service';
import { Observable, combineLatest as observableCombineLatest } from 'rxjs'; import { Observable, combineLatest as observableCombineLatest } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginatedList } from '../../../core/data/paginated-list.model';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { filter, map, switchMap, take } from 'rxjs/operators'; import { filter, map, switchMap, take } from 'rxjs/operators';
import { hasValue } from '../../../shared/empty.util'; import { hasValue } from '../../../shared/empty.util';
import { RestResponse } from '../../../core/cache/response.models';
import { zip } from 'rxjs/internal/observable/zip'; import { zip } from 'rxjs/internal/observable/zip';
import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { Route, Router } from '@angular/router'; import { Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model'; import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
import { toFindListOptions } from '../../../shared/pagination/pagination.utils'; import { toFindListOptions } from '../../../shared/pagination/pagination.utils';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { NoContent } from '../../../core/shared/NoContent.model';
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
@Component({ @Component({
selector: 'ds-metadata-registry', selector: 'ds-metadata-registry',
@@ -140,12 +141,12 @@ export class MetadataRegistryComponent {
const tasks$ = []; const tasks$ = [];
for (const schema of schemas) { for (const schema of schemas) {
if (hasValue(schema.id)) { if (hasValue(schema.id)) {
tasks$.push(this.registryService.deleteMetadataSchema(schema.id)); tasks$.push(this.registryService.deleteMetadataSchema(schema.id).pipe(getFirstCompletedRemoteData()));
} }
} }
zip(...tasks$).subscribe((responses: RestResponse[]) => { zip(...tasks$).subscribe((responses: Array<RemoteData<NoContent>>) => {
const successResponses = responses.filter((response: RestResponse) => response.isSuccessful); const successResponses = responses.filter((response: RemoteData<NoContent>) => response.hasSucceeded);
const failedResponses = responses.filter((response: RestResponse) => !response.isSuccessful); const failedResponses = responses.filter((response: RemoteData<NoContent>) => response.hasFailed);
if (successResponses.length > 0) { if (successResponses.length > 0) {
this.showNotification(true, successResponses.length); this.showNotification(true, successResponses.length);
} }

View File

@@ -1,7 +1,7 @@
import { MetadataSchemaComponent } from './metadata-schema.component'; import { MetadataSchemaComponent } from './metadata-schema.component';
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { PaginatedList } from '../../../core/data/paginated-list'; import { buildPaginatedList } from '../../../core/data/paginated-list.model';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
@@ -100,11 +100,11 @@ describe('MetadataSchemaComponent', () => {
schema: createSuccessfulRemoteDataObject$(mockSchemasList[1]) schema: createSuccessfulRemoteDataObject$(mockSchemasList[1])
} }
]; ];
const mockSchemas = createSuccessfulRemoteDataObject$(new PaginatedList(null, mockSchemasList)); const mockSchemas = createSuccessfulRemoteDataObject$(buildPaginatedList(null, mockSchemasList));
/* tslint:disable:no-empty */ /* tslint:disable:no-empty */
const registryServiceStub = { const registryServiceStub = {
getMetadataSchemas: () => mockSchemas, getMetadataSchemas: () => mockSchemas,
getMetadataFieldsBySchema: (schema: MetadataSchema) => createSuccessfulRemoteDataObject$(new PaginatedList(null, mockFieldsList.filter((value) => value.id === 3 || value.id === 4))), getMetadataFieldsBySchema: (schema: MetadataSchema) => createSuccessfulRemoteDataObject$(buildPaginatedList(null, mockFieldsList.filter((value) => value.id === 3 || value.id === 4))),
getMetadataSchemaByPrefix: (schemaName: string) => createSuccessfulRemoteDataObject$(mockSchemasList.filter((value) => value.prefix === schemaName)[0]), getMetadataSchemaByPrefix: (schemaName: string) => createSuccessfulRemoteDataObject$(mockSchemasList.filter((value) => value.prefix === schemaName)[0]),
getActiveMetadataField: () => observableOf(undefined), getActiveMetadataField: () => observableOf(undefined),
getSelectedMetadataFields: () => observableOf([]), getSelectedMetadataFields: () => observableOf([]),

View File

@@ -3,21 +3,24 @@ import { RegistryService } from '../../../core/registry/registry.service';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { Observable, combineLatest as observableCombineLatest } from 'rxjs'; import { Observable, combineLatest as observableCombineLatest } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginatedList } from '../../../core/data/paginated-list.model';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { map, switchMap, take } from 'rxjs/operators'; import { map, switchMap, take } from 'rxjs/operators';
import { hasValue } from '../../../shared/empty.util'; import { hasValue } from '../../../shared/empty.util';
import { RestResponse } from '../../../core/cache/response.models';
import { zip } from 'rxjs/internal/observable/zip'; import { zip } from 'rxjs/internal/observable/zip';
import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { MetadataField } from '../../../core/metadata/metadata-field.model'; import { MetadataField } from '../../../core/metadata/metadata-field.model';
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model'; import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators'; import {
getFirstSucceededRemoteDataPayload,
getFirstCompletedRemoteData
} from '../../../core/shared/operators';
import { toFindListOptions } from '../../../shared/pagination/pagination.utils'; import { toFindListOptions } from '../../../shared/pagination/pagination.utils';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { combineLatest } from 'rxjs/internal/observable/combineLatest'; import { combineLatest } from 'rxjs/internal/observable/combineLatest';
import { followLink } from '../../../shared/utils/follow-link-config.model'; import { followLink } from '../../../shared/utils/follow-link-config.model';
import { NoContent } from '../../../core/shared/NoContent.model';
@Component({ @Component({
selector: 'ds-metadata-schema', selector: 'ds-metadata-schema',
@@ -92,7 +95,7 @@ export class MetadataSchemaComponent implements OnInit {
this.metadataFields$ = combineLatest(this.metadataSchema$, this.needsUpdate$).pipe( this.metadataFields$ = combineLatest(this.metadataSchema$, this.needsUpdate$).pipe(
switchMap(([schema, update]: [MetadataSchema, boolean]) => { switchMap(([schema, update]: [MetadataSchema, boolean]) => {
if (update) { if (update) {
return this.registryService.getMetadataFieldsBySchema(schema, toFindListOptions(this.config), followLink('schema')); return this.registryService.getMetadataFieldsBySchema(schema, toFindListOptions(this.config), true, followLink('schema'));
} }
}) })
); );
@@ -168,12 +171,12 @@ export class MetadataSchemaComponent implements OnInit {
const tasks$ = []; const tasks$ = [];
for (const field of fields) { for (const field of fields) {
if (hasValue(field.id)) { if (hasValue(field.id)) {
tasks$.push(this.registryService.deleteMetadataField(field.id)); tasks$.push(this.registryService.deleteMetadataField(field.id).pipe(getFirstCompletedRemoteData()));
} }
} }
zip(...tasks$).subscribe((responses: RestResponse[]) => { zip(...tasks$).subscribe((responses: Array<RemoteData<NoContent>>) => {
const successResponses = responses.filter((response: RestResponse) => response.isSuccessful); const successResponses = responses.filter((response: RemoteData<NoContent>) => response.hasSucceeded);
const failedResponses = responses.filter((response: RestResponse) => !response.isSuccessful); const failedResponses = responses.filter((response: RemoteData<NoContent>) => response.hasFailed);
if (successResponses.length > 0) { if (successResponses.length > 0) {
this.showNotification(true, successResponses.length); this.showNotification(true, successResponses.length);
} }

View File

@@ -2,11 +2,10 @@ import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { RemoteData } from '../core/data/remote-data'; import { RemoteData } from '../core/data/remote-data';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { find } from 'rxjs/operators';
import { hasValue } from '../shared/empty.util';
import { Bitstream } from '../core/shared/bitstream.model'; import { Bitstream } from '../core/shared/bitstream.model';
import { BitstreamDataService } from '../core/data/bitstream-data.service'; import { BitstreamDataService } from '../core/data/bitstream-data.service';
import {followLink, FollowLinkConfig} from '../shared/utils/follow-link-config.model'; import {followLink, FollowLinkConfig} from '../shared/utils/follow-link-config.model';
import { getFirstCompletedRemoteData } from '../core/shared/operators';
/** /**
* This class represents a resolver that requests a specific bitstream before the route is activated * This class represents a resolver that requests a specific bitstream before the route is activated
@@ -24,9 +23,9 @@ export class BitstreamPageResolver implements Resolve<RemoteData<Bitstream>> {
* or an error if something went wrong * or an error if something went wrong
*/ */
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Bitstream>> { resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Bitstream>> {
return this.bitstreamService.findById(route.params.id, ...this.followLinks) return this.bitstreamService.findById(route.params.id, false, ...this.followLinks)
.pipe( .pipe(
find((RD) => hasValue(RD.error) || RD.hasSucceeded), getFirstCompletedRemoteData(),
); );
} }
/** /**

View File

@@ -2,7 +2,6 @@ import { EditBitstreamPageComponent } from './edit-bitstream-page.component';
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { RemoteData } from '../../core/data/remote-data';
import { of as observableOf } from 'rxjs/internal/observable/of'; import { of as observableOf } from 'rxjs/internal/observable/of';
import {ActivatedRoute, Router} from '@angular/router'; import {ActivatedRoute, Router} from '@angular/router';
import { DynamicFormControlModel, DynamicFormService } from '@ng-dynamic-forms/core'; import { DynamicFormControlModel, DynamicFormService } from '@ng-dynamic-forms/core';
@@ -17,16 +16,15 @@ import { BitstreamFormat } from '../../core/shared/bitstream-format.model';
import { BitstreamFormatSupportLevel } from '../../core/shared/bitstream-format-support-level'; import { BitstreamFormatSupportLevel } from '../../core/shared/bitstream-format-support-level';
import { hasValue } from '../../shared/empty.util'; import { hasValue } from '../../shared/empty.util';
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms';
import { PaginatedList } from '../../core/data/paginated-list';
import { PageInfo } from '../../core/shared/page-info.model';
import { FileSizePipe } from '../../shared/utils/file-size-pipe'; import { FileSizePipe } from '../../shared/utils/file-size-pipe';
import { RestResponse } from '../../core/cache/response.models';
import { VarDirective } from '../../shared/utils/var.directive'; import { VarDirective } from '../../shared/utils/var.directive';
import { import {
createSuccessfulRemoteDataObject,
createSuccessfulRemoteDataObject$ createSuccessfulRemoteDataObject$
} from '../../shared/remote-data.utils'; } from '../../shared/remote-data.utils';
import {RouterStub} from '../../shared/testing/router.stub'; import {RouterStub} from '../../shared/testing/router.stub';
import { getItemEditRoute } from '../../+item-page/item-page-routing-paths'; import { getItemEditRoute } from '../../+item-page/item-page-routing-paths';
import { createPaginatedList } from '../../shared/testing/utils.test';
const infoNotification: INotification = new Notification('id', NotificationType.Info, 'info'); const infoNotification: INotification = new Notification('id', NotificationType.Info, 'info');
const warningNotification: INotification = new Notification('id', NotificationType.Warning, 'warning'); const warningNotification: INotification = new Notification('id', NotificationType.Warning, 'warning');
@@ -109,7 +107,7 @@ describe('EditBitstreamPageComponent', () => {
} }
] ]
}, },
format: observableOf(new RemoteData(false, false, true, null, selectedFormat)), format: createSuccessfulRemoteDataObject$(selectedFormat),
_links: { _links: {
self: 'bitstream-selflink' self: 'bitstream-selflink'
}, },
@@ -120,14 +118,14 @@ describe('EditBitstreamPageComponent', () => {
}) })
}); });
bitstreamService = jasmine.createSpyObj('bitstreamService', { bitstreamService = jasmine.createSpyObj('bitstreamService', {
findById: observableOf(new RemoteData(false, false, true, null, bitstream)), findById: createSuccessfulRemoteDataObject$(bitstream),
update: observableOf(new RemoteData(false, false, true, null, bitstream)), update: createSuccessfulRemoteDataObject$(bitstream),
updateFormat: observableOf(new RestResponse(true, 200, 'OK')), updateFormat: createSuccessfulRemoteDataObject$(bitstream),
commitUpdates: {}, commitUpdates: {},
patch: {} patch: {}
}); });
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', { bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
findAll: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), allFormats))) findAll: createSuccessfulRemoteDataObject$(createPaginatedList(allFormats))
}); });
const itemPageUrl = `fake-url/some-uuid`; const itemPageUrl = `fake-url/some-uuid`;
@@ -140,7 +138,7 @@ describe('EditBitstreamPageComponent', () => {
providers: [ providers: [
{ provide: NotificationsService, useValue: notificationsService }, { provide: NotificationsService, useValue: notificationsService },
{ provide: DynamicFormService, useValue: formService }, { provide: DynamicFormService, useValue: formService },
{ provide: ActivatedRoute, useValue: { data: observableOf({ bitstream: new RemoteData(false, false, true, null, bitstream) }), snapshot: { queryParams: {} } } }, { provide: ActivatedRoute, useValue: { data: observableOf({ bitstream: createSuccessfulRemoteDataObject(bitstream) }), snapshot: { queryParams: {} } } },
{ provide: BitstreamDataService, useValue: bitstreamService }, { provide: BitstreamDataService, useValue: bitstreamService },
{ provide: BitstreamFormatDataService, useValue: bitstreamFormatService }, { provide: BitstreamFormatDataService, useValue: bitstreamFormatService },
{ provide: Router, useValue: routerStub }, { provide: Router, useValue: routerStub },

View File

@@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
import { Bitstream } from '../../core/shared/bitstream.model'; import { Bitstream } from '../../core/shared/bitstream.model';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { map, mergeMap, switchMap} from 'rxjs/operators'; import { map, mergeMap, switchMap } from 'rxjs/operators';
import { combineLatest as observableCombineLatest, of as observableOf } from 'rxjs'; import { combineLatest as observableCombineLatest, of as observableOf } from 'rxjs';
import { Subscription } from 'rxjs/internal/Subscription'; import { Subscription } from 'rxjs/internal/Subscription';
import { import {
@@ -22,22 +22,22 @@ import {
getAllSucceededRemoteDataPayload, getAllSucceededRemoteDataPayload,
getFirstSucceededRemoteDataPayload, getFirstSucceededRemoteDataPayload,
getRemoteDataPayload, getRemoteDataPayload,
getSucceededRemoteData getFirstSucceededRemoteData,
getFirstCompletedRemoteData
} from '../../core/shared/operators'; } from '../../core/shared/operators';
import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NotificationsService } from '../../shared/notifications/notifications.service';
import { BitstreamFormatDataService } from '../../core/data/bitstream-format-data.service'; import { BitstreamFormatDataService } from '../../core/data/bitstream-format-data.service';
import { BitstreamFormat } from '../../core/shared/bitstream-format.model'; import { BitstreamFormat } from '../../core/shared/bitstream-format.model';
import { BitstreamFormatSupportLevel } from '../../core/shared/bitstream-format-support-level'; import { BitstreamFormatSupportLevel } from '../../core/shared/bitstream-format-support-level';
import { RestResponse } from '../../core/cache/response.models';
import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { hasValue, isNotEmpty } from '../../shared/empty.util';
import { Metadata } from '../../core/shared/metadata.utils'; import { Metadata } from '../../core/shared/metadata.utils';
import { Location } from '@angular/common'; import { Location } from '@angular/common';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { PaginatedList } from '../../core/data/paginated-list'; import { PaginatedList } from '../../core/data/paginated-list.model';
import { getItemEditRoute } from '../../+item-page/item-page-routing-paths'; import { getItemEditRoute } from '../../+item-page/item-page-routing-paths';
import {Bundle} from '../../core/shared/bundle.model'; import { Bundle } from '../../core/shared/bundle.model';
import {Item} from '../../core/shared/item.model'; import { Item } from '../../core/shared/item.model';
@Component({ @Component({
selector: 'ds-edit-bitstream-page', selector: 'ds-edit-bitstream-page',
@@ -299,12 +299,12 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
this.bitstreamFormatsRD$ = this.bitstreamFormatService.findAll(this.findAllOptions); this.bitstreamFormatsRD$ = this.bitstreamFormatService.findAll(this.findAllOptions);
const bitstream$ = this.bitstreamRD$.pipe( const bitstream$ = this.bitstreamRD$.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload() getRemoteDataPayload()
); );
const allFormats$ = this.bitstreamFormatsRD$.pipe( const allFormats$ = this.bitstreamFormatsRD$.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload() getRemoteDataPayload()
); );
@@ -438,16 +438,15 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
if (isNewFormat) { if (isNewFormat) {
bitstream$ = this.bitstreamService.updateFormat(this.bitstream, selectedFormat).pipe( bitstream$ = this.bitstreamService.updateFormat(this.bitstream, selectedFormat).pipe(
switchMap((formatResponse: RestResponse) => { getFirstCompletedRemoteData(),
if (hasValue(formatResponse) && !formatResponse.isSuccessful) { map((formatResponse: RemoteData<Bitstream>) => {
if (hasValue(formatResponse) && formatResponse.hasFailed) {
this.notificationsService.error( this.notificationsService.error(
this.translate.instant(this.NOTIFICATIONS_PREFIX + 'error.format.title'), this.translate.instant(this.NOTIFICATIONS_PREFIX + 'error.format.title'),
formatResponse.statusText formatResponse.errorMessage
); );
} else { } else {
return this.bitstreamService.findById(this.bitstream.id).pipe( return formatResponse.payload;
getFirstSucceededRemoteDataPayload()
);
} }
}) })
); );

View File

@@ -1,4 +1,4 @@
import { ChangeDetectorRef, Component, Inject } from '@angular/core'; import { ChangeDetectorRef, Component } from '@angular/core';
import { import {
BrowseByMetadataPageComponent, BrowseByMetadataPageComponent,
browseParamsToOptions browseParamsToOptions
@@ -43,7 +43,7 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
ngOnInit(): void { ngOnInit(): void {
this.startsWithType = StartsWithType.date; this.startsWithType = StartsWithType.date;
this.updatePage(new BrowseEntrySearchOptions(null, this.paginationConfig, this.sortConfig)); this.updatePage(new BrowseEntrySearchOptions(this.defaultBrowseId, this.paginationConfig, this.sortConfig));
this.subs.push( this.subs.push(
observableCombineLatest( observableCombineLatest(
this.route.params, this.route.params,

View File

@@ -12,7 +12,7 @@ import { of as observableOf } from 'rxjs/internal/observable/of';
import { NO_ERRORS_SCHEMA } from '@angular/core'; import { NO_ERRORS_SCHEMA } from '@angular/core';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { PaginatedList } from '../../core/data/paginated-list'; import { PaginatedList, buildPaginatedList } from '../../core/data/paginated-list.model';
import { PageInfo } from '../../core/shared/page-info.model'; import { PageInfo } from '../../core/shared/page-info.model';
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model'; import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
import { SortDirection } from '../../core/cache/models/sort-options.model'; import { SortDirection } from '../../core/cache/models/sort-options.model';
@@ -157,5 +157,5 @@ describe('BrowseByMetadataPageComponent', () => {
}); });
export function toRemoteData(objects: any[]): Observable<RemoteData<PaginatedList<any>>> { export function toRemoteData(objects: any[]): Observable<RemoteData<PaginatedList<any>>> {
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), objects)); return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), objects));
} }

View File

@@ -1,7 +1,7 @@
import {combineLatest as observableCombineLatest, merge as observableMerge, Observable, Subscription } from 'rxjs'; import { combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs';
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { PaginatedList } from '../../core/data/paginated-list'; import { PaginatedList } from '../../core/data/paginated-list.model';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
@@ -10,10 +10,9 @@ import { BrowseService } from '../../core/browse/browse.service';
import { BrowseEntry } from '../../core/shared/browse-entry.model'; import { BrowseEntry } from '../../core/shared/browse-entry.model';
import { Item } from '../../core/shared/item.model'; import { Item } from '../../core/shared/item.model';
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model'; import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
import { getSucceededRemoteData } from '../../core/shared/operators'; import { getFirstSucceededRemoteData } from '../../core/shared/operators';
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { take } from 'rxjs/operators';
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator'; import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
import { BrowseByType, rendersBrowseBy } from '../+browse-by-switcher/browse-by-decorator'; import { BrowseByType, rendersBrowseBy } from '../+browse-by-switcher/browse-by-decorator';
@@ -105,7 +104,7 @@ export class BrowseByMetadataPageComponent implements OnInit {
} }
ngOnInit(): void { ngOnInit(): void {
this.updatePage(new BrowseEntrySearchOptions(null, this.paginationConfig, this.sortConfig)); this.updatePage(new BrowseEntrySearchOptions(this.defaultBrowseId, this.paginationConfig, this.sortConfig));
this.subs.push( this.subs.push(
observableCombineLatest( observableCombineLatest(
this.route.params, this.route.params,
@@ -169,7 +168,7 @@ export class BrowseByMetadataPageComponent implements OnInit {
updateParent(scope: string) { updateParent(scope: string) {
if (hasValue(scope)) { if (hasValue(scope)) {
this.parent$ = this.dsoService.findById(scope).pipe( this.parent$ = this.dsoService.findById(scope).pipe(
getSucceededRemoteData() getFirstSucceededRemoteData()
); );
} }
} }
@@ -179,11 +178,11 @@ export class BrowseByMetadataPageComponent implements OnInit {
*/ */
goPrev() { goPrev() {
if (this.items$) { if (this.items$) {
this.items$.pipe(take(1)).subscribe((items) => { this.items$.pipe(getFirstSucceededRemoteData()).subscribe((items) => {
this.items$ = this.browseService.getPrevBrowseItems(items); this.items$ = this.browseService.getPrevBrowseItems(items);
}); });
} else if (this.browseEntries$) { } else if (this.browseEntries$) {
this.browseEntries$.pipe(take(1)).subscribe((entries) => { this.browseEntries$.pipe(getFirstSucceededRemoteData()).subscribe((entries) => {
this.browseEntries$ = this.browseService.getPrevBrowseEntries(entries); this.browseEntries$ = this.browseService.getPrevBrowseEntries(entries);
}); });
} }
@@ -194,11 +193,11 @@ export class BrowseByMetadataPageComponent implements OnInit {
*/ */
goNext() { goNext() {
if (this.items$) { if (this.items$) {
this.items$.pipe(take(1)).subscribe((items) => { this.items$.pipe(getFirstSucceededRemoteData()).subscribe((items) => {
this.items$ = this.browseService.getNextBrowseItems(items); this.items$ = this.browseService.getNextBrowseItems(items);
}); });
} else if (this.browseEntries$) { } else if (this.browseEntries$) {
this.browseEntries$.pipe(take(1)).subscribe((entries) => { this.browseEntries$.pipe(getFirstSucceededRemoteData()).subscribe((entries) => {
this.browseEntries$ = this.browseService.getNextBrowseEntries(entries); this.browseEntries$ = this.browseService.getNextBrowseEntries(entries);
}); });
} }

View File

@@ -32,7 +32,7 @@ export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent {
ngOnInit(): void { ngOnInit(): void {
this.sortConfig = new SortOptions('dc.title', SortDirection.ASC); this.sortConfig = new SortOptions('dc.title', SortDirection.ASC);
this.updatePage(new BrowseEntrySearchOptions(null, this.paginationConfig, this.sortConfig)); this.updatePage(new BrowseEntrySearchOptions(this.defaultBrowseId, this.paginationConfig, this.sortConfig));
this.subs.push( this.subs.push(
observableCombineLatest( observableCombineLatest(
this.route.params, this.route.params,

View File

@@ -6,7 +6,7 @@ import { Collection } from '../core/shared/collection.model';
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { BreadcrumbConfig } from '../breadcrumbs/breadcrumb/breadcrumb-config.model'; import { BreadcrumbConfig } from '../breadcrumbs/breadcrumb/breadcrumb-config.model';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { getRemoteDataPayload, getSucceededRemoteData } from '../core/shared/operators'; import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../core/shared/operators';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { hasValue } from '../shared/empty.util'; import { hasValue } from '../shared/empty.util';
import { getDSORoute } from '../app-routing-paths'; import { getDSORoute } from '../app-routing-paths';
@@ -29,7 +29,7 @@ export class BrowseByDSOBreadcrumbResolver {
const uuid = route.queryParams.scope; const uuid = route.queryParams.scope;
if (hasValue(uuid)) { if (hasValue(uuid)) {
return this.dataService.findById(uuid).pipe( return this.dataService.findById(uuid).pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
map((object: Community | Collection) => { map((object: Community | Collection) => {
return { provider: this.breadcrumbService, key: object, url: getDSORoute(object) }; return { provider: this.breadcrumbService, key: object, url: getDSORoute(object) };

View File

@@ -3,7 +3,7 @@ import { Inject, Injectable } from '@angular/core';
import { DSpaceObjectDataService } from '../core/data/dspace-object-data.service'; import { DSpaceObjectDataService } from '../core/data/dspace-object-data.service';
import { hasNoValue, hasValue } from '../shared/empty.util'; import { hasNoValue, hasValue } from '../shared/empty.util';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { getSucceededRemoteData } from '../core/shared/operators'; import { getFirstSucceededRemoteData } from '../core/shared/operators';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { environment } from '../../environments/environment'; import { environment } from '../../environments/environment';
@@ -32,7 +32,7 @@ export class BrowseByGuard implements CanActivate {
const value = route.queryParams.value; const value = route.queryParams.value;
const metadataTranslated = this.translate.instant('browse.metadata.' + id); const metadataTranslated = this.translate.instant('browse.metadata.' + id);
if (hasValue(scope)) { if (hasValue(scope)) {
const dsoAndMetadata$ = this.dsoService.findById(scope).pipe(getSucceededRemoteData()); const dsoAndMetadata$ = this.dsoService.findById(scope).pipe(getFirstSucceededRemoteData());
return dsoAndMetadata$.pipe( return dsoAndMetadata$.pipe(
map((dsoRD) => { map((dsoRD) => {
const name = dsoRD.payload.name; const name = dsoRD.payload.name;

View File

@@ -1,4 +1,3 @@
import { filter, tap } from 'rxjs/operators';
import { CollectionItemMapperComponent } from './collection-item-mapper.component'; import { CollectionItemMapperComponent } from './collection-item-mapper.component';
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
@@ -22,8 +21,6 @@ import { EventEmitter } from '@angular/core';
import { HostWindowService } from '../../shared/host-window.service'; import { HostWindowService } from '../../shared/host-window.service';
import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub'; import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { PaginatedList } from '../../core/data/paginated-list';
import { PageInfo } from '../../core/shared/page-info.model';
import { CollectionDataService } from '../../core/data/collection-data.service'; import { CollectionDataService } from '../../core/data/collection-data.service';
import { PaginationComponent } from '../../shared/pagination/pagination.component'; import { PaginationComponent } from '../../shared/pagination/pagination.component';
import { EnumKeysPipe } from '../../shared/utils/enum-keys-pipe'; import { EnumKeysPipe } from '../../shared/utils/enum-keys-pipe';
@@ -39,6 +36,12 @@ import { LoadingComponent } from '../../shared/loading/loading.component';
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service'; import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
import { SearchService } from '../../core/shared/search/search.service'; import { SearchService } from '../../core/shared/search/search.service';
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model'; import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
import {
createFailedRemoteDataObject$,
createSuccessfulRemoteDataObject,
createSuccessfulRemoteDataObject$
} from '../../shared/remote-data.utils';
import { createPaginatedList } from '../../shared/testing/utils.test';
describe('CollectionItemMapperComponent', () => { describe('CollectionItemMapperComponent', () => {
let comp: CollectionItemMapperComponent; let comp: CollectionItemMapperComponent;
@@ -60,7 +63,7 @@ describe('CollectionItemMapperComponent', () => {
} }
} }
}); });
const mockCollectionRD: RemoteData<Collection> = new RemoteData<Collection>(false, false, true, null, mockCollection); const mockCollectionRD: RemoteData<Collection> = createSuccessfulRemoteDataObject(mockCollection);
const mockSearchOptions = of(new PaginatedSearchOptions({ const mockSearchOptions = of(new PaginatedSearchOptions({
pagination: Object.assign(new PaginationComponentOptions(), { pagination: Object.assign(new PaginationComponentOptions(), {
id: 'search-page-configuration', id: 'search-page-configuration',
@@ -81,7 +84,7 @@ describe('CollectionItemMapperComponent', () => {
paginatedSearchOptions: mockSearchOptions paginatedSearchOptions: mockSearchOptions
}; };
const itemDataServiceStub = { const itemDataServiceStub = {
mapToCollection: () => of(new RestResponse(true, 200, 'OK')) mapToCollection: () => createSuccessfulRemoteDataObject$({})
}; };
const activatedRouteStub = new ActivatedRouteStub({}, { dso: mockCollectionRD }); const activatedRouteStub = new ActivatedRouteStub({}, { dso: mockCollectionRD });
const translateServiceStub = { const translateServiceStub = {
@@ -90,7 +93,7 @@ describe('CollectionItemMapperComponent', () => {
onTranslationChange: new EventEmitter(), onTranslationChange: new EventEmitter(),
onDefaultLangChange: new EventEmitter() onDefaultLangChange: new EventEmitter()
}; };
const emptyList = new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), [])); const emptyList = createSuccessfulRemoteDataObject(createPaginatedList([]));
const searchServiceStub = Object.assign(new SearchServiceStub(), { const searchServiceStub = Object.assign(new SearchServiceStub(), {
search: () => of(emptyList), search: () => of(emptyList),
/* tslint:disable:no-empty */ /* tslint:disable:no-empty */
@@ -167,7 +170,7 @@ describe('CollectionItemMapperComponent', () => {
}); });
it('should display an error message if at least one mapping was unsuccessful', () => { it('should display an error message if at least one mapping was unsuccessful', () => {
spyOn(itemDataService, 'mapToCollection').and.returnValue(of(new RestResponse(false, 404, 'Not Found'))); spyOn(itemDataService, 'mapToCollection').and.returnValue(createFailedRemoteDataObject$('Not Found', 404));
comp.mapItems(ids); comp.mapItems(ids);
expect(notificationsService.success).not.toHaveBeenCalled(); expect(notificationsService.success).not.toHaveBeenCalled();
expect(notificationsService.error).toHaveBeenCalled(); expect(notificationsService.error).toHaveBeenCalled();

View File

@@ -5,9 +5,13 @@ import { fadeIn, fadeInOut } from '../../shared/animations/fade';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { Collection } from '../../core/shared/collection.model'; import { Collection } from '../../core/shared/collection.model';
import { PaginatedList } from '../../core/data/paginated-list'; import { PaginatedList } from '../../core/data/paginated-list.model';
import { map, startWith, switchMap, take } from 'rxjs/operators'; import { map, startWith, switchMap, take } from 'rxjs/operators';
import { getRemoteDataPayload, getSucceededRemoteData, toDSpaceObjectListRD } from '../../core/shared/operators'; import {
getRemoteDataPayload,
getFirstSucceededRemoteData,
toDSpaceObjectListRD
} from '../../core/shared/operators';
import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model'; import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
@@ -16,13 +20,13 @@ import { ItemDataService } from '../../core/data/item-data.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { CollectionDataService } from '../../core/data/collection-data.service'; import { CollectionDataService } from '../../core/data/collection-data.service';
import { isNotEmpty } from '../../shared/empty.util'; import { isNotEmpty } from '../../shared/empty.util';
import { RestResponse } from '../../core/cache/response.models';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { SEARCH_CONFIG_SERVICE } from '../../+my-dspace-page/my-dspace-page.component'; import { SEARCH_CONFIG_SERVICE } from '../../+my-dspace-page/my-dspace-page.component';
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service'; import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model'; import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
import { SearchService } from '../../core/shared/search/search.service'; import { SearchService } from '../../core/shared/search/search.service';
import { followLink } from '../../shared/utils/follow-link-config.model'; import { followLink } from '../../shared/utils/follow-link-config.model';
import { NoContent } from '../../core/shared/NoContent.model';
@Component({ @Component({
selector: 'ds-collection-item-mapper', selector: 'ds-collection-item-mapper',
@@ -102,7 +106,7 @@ export class CollectionItemMapperComponent implements OnInit {
} }
ngOnInit(): void { ngOnInit(): void {
this.collectionRD$ = this.route.data.pipe(map((data) => data.dso)).pipe(getSucceededRemoteData()) as Observable<RemoteData<Collection>>; this.collectionRD$ = this.route.data.pipe(map((data) => data.dso)).pipe(getFirstSucceededRemoteData()) as Observable<RemoteData<Collection>>;
this.searchOptions$ = this.searchConfigService.paginatedSearchOptions; this.searchOptions$ = this.searchConfigService.paginatedSearchOptions;
this.loadItemLists(); this.loadItemLists();
} }
@@ -151,7 +155,7 @@ export class CollectionItemMapperComponent implements OnInit {
*/ */
mapItems(ids: string[], remove?: boolean) { mapItems(ids: string[], remove?: boolean) {
const responses$ = this.collectionRD$.pipe( const responses$ = this.collectionRD$.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
map((collectionRD: RemoteData<Collection>) => collectionRD.payload), map((collectionRD: RemoteData<Collection>) => collectionRD.payload),
switchMap((collection: Collection) => switchMap((collection: Collection) =>
observableCombineLatest(ids.map((id: string) => observableCombineLatest(ids.map((id: string) =>
@@ -168,12 +172,12 @@ export class CollectionItemMapperComponent implements OnInit {
* @param {Observable<RestResponse[]>} responses$ The responses after adding/removing a mapping * @param {Observable<RestResponse[]>} responses$ The responses after adding/removing a mapping
* @param {boolean} remove Whether or not the goal was to remove mappings * @param {boolean} remove Whether or not the goal was to remove mappings
*/ */
private showNotifications(responses$: Observable<RestResponse[]>, remove?: boolean) { private showNotifications(responses$: Observable<Array<RemoteData<NoContent>>>, remove?: boolean) {
const messageInsertion = remove ? 'unmap' : 'map'; const messageInsertion = remove ? 'unmap' : 'map';
responses$.subscribe((responses: RestResponse[]) => { responses$.subscribe((responses: Array<RemoteData<NoContent>>) => {
const successful = responses.filter((response: RestResponse) => response.isSuccessful); const successful = responses.filter((response: RemoteData<any>) => response.hasSucceeded);
const unsuccessful = responses.filter((response: RestResponse) => !response.isSuccessful); const unsuccessful = responses.filter((response: RemoteData<any>) => response.hasFailed);
if (successful.length > 0) { if (successful.length > 0) {
const successMessages = observableCombineLatest( const successMessages = observableCombineLatest(
this.translateService.get(`collection.edit.item-mapper.notifications.${messageInsertion}.success.head`), this.translateService.get(`collection.edit.item-mapper.notifications.${messageInsertion}.success.head`),
@@ -246,7 +250,7 @@ export class CollectionItemMapperComponent implements OnInit {
*/ */
onCancel() { onCancel() {
this.collectionRD$.pipe( this.collectionRD$.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
take(1) take(1)
).subscribe((collection: Collection) => { ).subscribe((collection: Collection) => {

View File

@@ -1,12 +1,12 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { BehaviorSubject, of as observableOf, Observable, Subject } from 'rxjs'; import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { filter, flatMap, map, startWith, switchMap, take, tap } from 'rxjs/operators'; import { filter, flatMap, map, startWith, switchMap, take } from 'rxjs/operators';
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model'; import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
import { SearchService } from '../core/shared/search/search.service'; import { SearchService } from '../core/shared/search/search.service';
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
import { CollectionDataService } from '../core/data/collection-data.service'; import { CollectionDataService } from '../core/data/collection-data.service';
import { PaginatedList } from '../core/data/paginated-list'; import { PaginatedList } from '../core/data/paginated-list.model';
import { RemoteData } from '../core/data/remote-data'; import { RemoteData } from '../core/data/remote-data';
import { MetadataService } from '../core/metadata/metadata.service'; import { MetadataService } from '../core/metadata/metadata.service';
@@ -16,13 +16,13 @@ import { Collection } from '../core/shared/collection.model';
import { DSpaceObjectType } from '../core/shared/dspace-object-type.model'; import { DSpaceObjectType } from '../core/shared/dspace-object-type.model';
import { Item } from '../core/shared/item.model'; import { Item } from '../core/shared/item.model';
import { import {
getSucceededRemoteData, getFirstSucceededRemoteData,
redirectOn4xx, redirectOn4xx,
toDSpaceObjectListRD toDSpaceObjectListRD
} from '../core/shared/operators'; } from '../core/shared/operators';
import { fadeIn, fadeInOut } from '../shared/animations/fade'; import { fadeIn, fadeInOut } from '../shared/animations/fade';
import { hasNoValue, hasValue, isNotEmpty } from '../shared/empty.util'; import { hasValue, isNotEmpty } from '../shared/empty.util';
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
import { AuthService } from '../core/auth/auth.service'; import { AuthService } from '../core/auth/auth.service';
@@ -81,7 +81,7 @@ export class CollectionPageComponent implements OnInit {
this.itemRD$ = this.paginationChanges$.pipe( this.itemRD$ = this.paginationChanges$.pipe(
switchMap((dto) => this.collectionRD$.pipe( switchMap((dto) => this.collectionRD$.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
map((rd) => rd.payload.id), map((rd) => rd.payload.id),
switchMap((id: string) => { switchMap((id: string) => {
return this.searchService.search( return this.searchService.search(

View File

@@ -1,6 +1,6 @@
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
import { of as observableOf } from 'rxjs';
import { CollectionPageResolver } from './collection-page.resolver'; import { CollectionPageResolver } from './collection-page.resolver';
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
describe('CollectionPageResolver', () => { describe('CollectionPageResolver', () => {
describe('resolve', () => { describe('resolve', () => {
@@ -10,17 +10,18 @@ describe('CollectionPageResolver', () => {
beforeEach(() => { beforeEach(() => {
collectionService = { collectionService = {
findById: (id: string) => observableOf({ payload: { id }, hasSucceeded: true }) findById: (id: string) => createSuccessfulRemoteDataObject$({ id })
}; };
resolver = new CollectionPageResolver(collectionService); resolver = new CollectionPageResolver(collectionService);
}); });
it('should resolve a collection with the correct id', () => { it('should resolve a collection with the correct id', (done) => {
resolver.resolve({ params: { id: uuid } } as any, undefined) resolver.resolve({ params: { id: uuid } } as any, undefined)
.pipe(first()) .pipe(first())
.subscribe( .subscribe(
(resolved) => { (resolved) => {
expect(resolved.payload.id).toEqual(uuid); expect(resolved.payload.id).toEqual(uuid);
done();
} }
); );
}); });

View File

@@ -4,9 +4,8 @@ import { Collection } from '../core/shared/collection.model';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { CollectionDataService } from '../core/data/collection-data.service'; import { CollectionDataService } from '../core/data/collection-data.service';
import { RemoteData } from '../core/data/remote-data'; import { RemoteData } from '../core/data/remote-data';
import { find } from 'rxjs/operators';
import { hasValue } from '../shared/empty.util';
import { followLink } from '../shared/utils/follow-link-config.model'; import { followLink } from '../shared/utils/follow-link-config.model';
import { getFirstCompletedRemoteData } from '../core/shared/operators';
/** /**
* This class represents a resolver that requests a specific collection before the route is activated * This class represents a resolver that requests a specific collection before the route is activated
@@ -24,8 +23,8 @@ export class CollectionPageResolver implements Resolve<RemoteData<Collection>> {
* or an error if something went wrong * or an error if something went wrong
*/ */
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Collection>> { resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Collection>> {
return this.collectionService.findById(route.params.id, followLink('logo')).pipe( return this.collectionService.findById(route.params.id, false, followLink('logo')).pipe(
find((RD) => hasValue(RD.error) || RD.hasSucceeded), getFirstCompletedRemoteData()
); );
} }
} }

View File

@@ -18,7 +18,7 @@ describe('CreateCollectionPageGuard', () => {
} else if (id === 'invalid-id') { } else if (id === 'invalid-id') {
return createSuccessfulRemoteDataObject$(undefined); return createSuccessfulRemoteDataObject$(undefined);
} else if (id === 'error-id') { } else if (id === 'error-id') {
return createFailedRemoteDataObject$(new Community()); return createFailedRemoteDataObject$('not found', 404);
} }
} }
}; };

View File

@@ -5,8 +5,9 @@ import { hasNoValue, hasValue } from '../../shared/empty.util';
import { CommunityDataService } from '../../core/data/community-data.service'; import { CommunityDataService } from '../../core/data/community-data.service';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { Community } from '../../core/shared/community.model'; import { Community } from '../../core/shared/community.model';
import { map, tap, find } from 'rxjs/operators'; import { map, tap } from 'rxjs/operators';
import { Observable, of as observableOf } from 'rxjs'; import { Observable, of as observableOf } from 'rxjs';
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
/** /**
* Prevent creation of a collection without a parent community provided * Prevent creation of a collection without a parent community provided
@@ -30,7 +31,7 @@ export class CreateCollectionPageGuard implements CanActivate {
} }
return this.communityService.findById(parentID) return this.communityService.findById(parentID)
.pipe( .pipe(
find((communityRD: RemoteData<Community>) => hasValue(communityRD.payload) || hasValue(communityRD.error)), getFirstCompletedRemoteData(),
map((communityRD: RemoteData<Community>) => hasValue(communityRD) && communityRD.hasSucceeded && hasValue(communityRD.payload)), map((communityRD: RemoteData<Community>) => hasValue(communityRD) && communityRD.hasSucceeded && hasValue(communityRD.payload)),
tap((isValid: boolean) => { tap((isValid: boolean) => {
if (!isValid) { if (!isValid) {

View File

@@ -7,7 +7,7 @@ import { ItemTemplateDataService } from '../../../core/data/item-template-data.s
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../core/shared/operators'; import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../../../core/shared/operators';
import { switchMap, take } from 'rxjs/operators'; import { switchMap, take } from 'rxjs/operators';
import { combineLatest as combineLatestObservable } from 'rxjs'; import { combineLatest as combineLatestObservable } from 'rxjs';
import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service';
@@ -54,7 +54,7 @@ export class CollectionMetadataComponent extends ComcolMetadataComponent<Collect
*/ */
initTemplateItem() { initTemplateItem() {
this.itemTemplateRD$ = this.dsoRD$.pipe( this.itemTemplateRD$ = this.dsoRD$.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
switchMap((collection: Collection) => this.itemTemplateService.findByCollectionID(collection.uuid)) switchMap((collection: Collection) => this.itemTemplateService.findByCollectionID(collection.uuid))
); );
@@ -65,13 +65,13 @@ export class CollectionMetadataComponent extends ComcolMetadataComponent<Collect
*/ */
addItemTemplate() { addItemTemplate() {
const collection$ = this.dsoRD$.pipe( const collection$ = this.dsoRD$.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
take(1) take(1)
); );
const template$ = collection$.pipe( const template$ = collection$.pipe(
switchMap((collection: Collection) => this.itemTemplateService.create(new Item(), collection.uuid)), switchMap((collection: Collection) => this.itemTemplateService.create(new Item(), collection.uuid)),
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
take(1) take(1)
); );
@@ -86,13 +86,13 @@ export class CollectionMetadataComponent extends ComcolMetadataComponent<Collect
*/ */
deleteItemTemplate() { deleteItemTemplate() {
const collection$ = this.dsoRD$.pipe( const collection$ = this.dsoRD$.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
take(1) take(1)
); );
const template$ = collection$.pipe( const template$ = collection$.pipe(
switchMap((collection: Collection) => this.itemTemplateService.findByCollectionID(collection.uuid)), switchMap((collection: Collection) => this.itemTemplateService.findByCollectionID(collection.uuid)),
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
take(1) take(1)
); );

View File

@@ -1,5 +1,5 @@
<ds-comcol-role <ds-comcol-role
*ngFor="let comcolRole of getComcolRoles() | async" *ngFor="let comcolRole of comcolRoles$ | async"
[dso]="collection$ | async" [dso]="collection$ | async"
[comcolRole]="comcolRole" [comcolRole]="comcolRole"
> >

View File

@@ -11,6 +11,7 @@ import { SharedModule } from '../../../shared/shared.module';
import { GroupDataService } from '../../../core/eperson/group-data.service'; import { GroupDataService } from '../../../core/eperson/group-data.service';
import { RequestService } from '../../../core/data/request.service'; import { RequestService } from '../../../core/data/request.service';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
describe('CollectionRolesComponent', () => { describe('CollectionRolesComponent', () => {
@@ -23,11 +24,7 @@ describe('CollectionRolesComponent', () => {
const route = { const route = {
parent: { parent: {
data: observableOf({ data: observableOf({
dso: new RemoteData( dso: createSuccessfulRemoteDataObject(
false,
false,
true,
undefined,
Object.assign(new Collection(), { Object.assign(new Collection(), {
_links: { _links: {
irrelevant: { irrelevant: {
@@ -52,25 +49,18 @@ describe('CollectionRolesComponent', () => {
}, },
], ],
}, },
}), })
), ),
}) })
} }
}; };
const requestService = { const requestService = {
hasByHrefObservable: () => observableOf(true), hasByHref$: () => observableOf(true),
}; };
const groupDataService = { const groupDataService = {
findByHref: () => observableOf(new RemoteData( findByHref: () => createSuccessfulRemoteDataObject$({}),
false,
false,
true,
undefined,
{},
200,
)),
}; };
TestBed.configureTestingModule({ TestBed.configureTestingModule({

View File

@@ -4,7 +4,7 @@ import { Observable } from 'rxjs';
import { first, map } from 'rxjs/operators'; import { first, map } from 'rxjs/operators';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { Collection } from '../../../core/shared/collection.model'; import { Collection } from '../../../core/shared/collection.model';
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../core/shared/operators'; import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../../../core/shared/operators';
import { HALLink } from '../../../core/shared/hal-link.model'; import { HALLink } from '../../../core/shared/hal-link.model';
/** /**
@@ -18,21 +18,33 @@ export class CollectionRolesComponent implements OnInit {
dsoRD$: Observable<RemoteData<Collection>>; dsoRD$: Observable<RemoteData<Collection>>;
/**
* The collection to manage, as an observable.
*/
get collection$(): Observable<Collection> {
return this.dsoRD$.pipe(
getSucceededRemoteData(),
getRemoteDataPayload(),
)
}
/** /**
* The different roles for the collection, as an observable. * The different roles for the collection, as an observable.
*/ */
getComcolRoles(): Observable<HALLink[]> { comcolRoles$: Observable<HALLink[]>
return this.collection$.pipe(
/**
* The collection to manage, as an observable.
*/
collection$: Observable<Collection>
constructor(
protected route: ActivatedRoute,
) {
}
ngOnInit(): void {
this.dsoRD$ = this.route.parent.data.pipe(
first(),
map((data) => data.dso),
);
this.collection$ = this.dsoRD$.pipe(
getFirstSucceededRemoteData(),
getRemoteDataPayload(),
);
this.comcolRoles$ = this.collection$.pipe(
map((collection) => [ map((collection) => [
{ {
name: 'collection-admin', name: 'collection-admin',
@@ -54,16 +66,4 @@ export class CollectionRolesComponent implements OnInit {
]), ]),
); );
} }
constructor(
protected route: ActivatedRoute,
) {
}
ngOnInit(): void {
this.dsoRD$ = this.route.parent.data.pipe(
first(),
map((data) => data.dso),
);
}
} }

View File

@@ -17,9 +17,9 @@ import { FormControl, FormGroup } from '@angular/forms';
import { RouterStub } from '../../../shared/testing/router.stub'; import { RouterStub } from '../../../shared/testing/router.stub';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { Collection } from '../../../core/shared/collection.model'; import { Collection } from '../../../core/shared/collection.model';
import { RemoteData } from '../../../core/data/remote-data';
import { CollectionDataService } from '../../../core/data/collection-data.service'; import { CollectionDataService } from '../../../core/data/collection-data.service';
import { RequestService } from '../../../core/data/request.service'; import { RequestService } from '../../../core/data/request.service';
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
const infoNotification: INotification = new Notification('id', NotificationType.Info, 'info'); const infoNotification: INotification = new Notification('id', NotificationType.Info, 'info');
const warningNotification: INotification = new Notification('id', NotificationType.Warning, 'warning'); const warningNotification: INotification = new Notification('id', NotificationType.Warning, 'warning');
@@ -111,7 +111,7 @@ describe('CollectionSourceComponent', () => {
uuid: 'fake-collection-id' uuid: 'fake-collection-id'
}); });
collectionService = jasmine.createSpyObj('collectionService', { collectionService = jasmine.createSpyObj('collectionService', {
getContentSource: observableOf(contentSource), getContentSource: createSuccessfulRemoteDataObject$(contentSource),
updateContentSource: observableOf(contentSource), updateContentSource: observableOf(contentSource),
getHarvesterEndpoint: observableOf('harvester-endpoint') getHarvesterEndpoint: observableOf('harvester-endpoint')
}); });
@@ -125,7 +125,7 @@ describe('CollectionSourceComponent', () => {
{ provide: NotificationsService, useValue: notificationsService }, { provide: NotificationsService, useValue: notificationsService },
{ provide: Location, useValue: location }, { provide: Location, useValue: location },
{ provide: DynamicFormService, useValue: formService }, { provide: DynamicFormService, useValue: formService },
{ provide: ActivatedRoute, useValue: { parent: { data: observableOf({ dso: new RemoteData(false, false, true, null, collection) }) } } }, { provide: ActivatedRoute, useValue: { parent: { data: observableOf({ dso: createSuccessfulRemoteDataObject(collection) }) } } },
{ provide: Router, useValue: router }, { provide: Router, useValue: router },
{ provide: CollectionDataService, useValue: collectionService }, { provide: CollectionDataService, useValue: collectionService },
{ provide: RequestService, useValue: requestService } { provide: RequestService, useValue: requestService }

View File

@@ -27,7 +27,7 @@ import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/obj
import { Subscription } from 'rxjs/internal/Subscription'; import { Subscription } from 'rxjs/internal/Subscription';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { CollectionDataService } from '../../../core/data/collection-data.service'; import { CollectionDataService } from '../../../core/data/collection-data.service';
import { getSucceededRemoteData } from '../../../core/shared/operators'; import { getFirstSucceededRemoteData, getFirstCompletedRemoteData } from '../../../core/shared/operators';
import { MetadataConfig } from '../../../core/shared/metadata-config.model'; import { MetadataConfig } from '../../../core/shared/metadata-config.model';
import { INotification } from '../../../shared/notifications/models/notification.model'; import { INotification } from '../../../shared/notifications/models/notification.model';
import { RequestService } from '../../../core/data/request.service'; import { RequestService } from '../../../core/data/request.service';
@@ -255,12 +255,12 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
this.collectionRD$ = this.route.parent.data.pipe(first(), map((data) => data.dso)); this.collectionRD$ = this.route.parent.data.pipe(first(), map((data) => data.dso));
this.collectionRD$.pipe( this.collectionRD$.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
map((col) => col.payload.uuid), map((col) => col.payload.uuid),
switchMap((uuid) => this.collectionService.getContentSource(uuid)), switchMap((uuid) => this.collectionService.getContentSource(uuid)),
take(1) getFirstCompletedRemoteData(),
).subscribe((contentSource: ContentSource) => { ).subscribe((rd: RemoteData<ContentSource>) => {
this.initializeOriginalContentSource(contentSource); this.initializeOriginalContentSource(rd.payload);
}); });
this.updateFieldTranslations(); this.updateFieldTranslations();
@@ -376,7 +376,7 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
onSubmit() { onSubmit() {
// Remove cached harvester request to allow for latest harvester to be displayed when switching tabs // Remove cached harvester request to allow for latest harvester to be displayed when switching tabs
this.collectionRD$.pipe( this.collectionRD$.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
map((col) => col.payload.uuid), map((col) => col.payload.uuid),
switchMap((uuid) => this.collectionService.getHarvesterEndpoint(uuid)), switchMap((uuid) => this.collectionService.getHarvesterEndpoint(uuid)),
take(1) take(1)
@@ -384,7 +384,7 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
// Update harvester // Update harvester
this.collectionRD$.pipe( this.collectionRD$.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
map((col) => col.payload.uuid), map((col) => col.payload.uuid),
switchMap((uuid) => this.collectionService.updateContentSource(uuid, this.contentSource)), switchMap((uuid) => this.collectionService.updateContentSource(uuid, this.contentSource)),
take(1) take(1)

View File

@@ -1,6 +1,6 @@
import { of as observableOf } from 'rxjs/internal/observable/of';
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
import { ItemTemplatePageResolver } from './item-template-page.resolver'; import { ItemTemplatePageResolver } from './item-template-page.resolver';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
describe('ItemTemplatePageResolver', () => { describe('ItemTemplatePageResolver', () => {
describe('resolve', () => { describe('resolve', () => {
@@ -10,17 +10,18 @@ describe('ItemTemplatePageResolver', () => {
beforeEach(() => { beforeEach(() => {
itemTemplateService = { itemTemplateService = {
findByCollectionID: (id: string) => observableOf({ payload: { id }, hasSucceeded: true }) findByCollectionID: (id: string) => createSuccessfulRemoteDataObject$({ id })
}; };
resolver = new ItemTemplatePageResolver(itemTemplateService); resolver = new ItemTemplatePageResolver(itemTemplateService);
}); });
it('should resolve an item template with the correct id', () => { it('should resolve an item template with the correct id', (done) => {
resolver.resolve({ params: { id: uuid } } as any, undefined) resolver.resolve({ params: { id: uuid } } as any, undefined)
.pipe(first()) .pipe(first())
.subscribe( .subscribe(
(resolved) => { (resolved) => {
expect(resolved.payload.id).toEqual(uuid); expect(resolved.payload.id).toEqual(uuid);
done();
} }
); );
}); });

View File

@@ -4,9 +4,8 @@ import { RemoteData } from '../../core/data/remote-data';
import { Item } from '../../core/shared/item.model'; import { Item } from '../../core/shared/item.model';
import { ItemTemplateDataService } from '../../core/data/item-template-data.service'; import { ItemTemplateDataService } from '../../core/data/item-template-data.service';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { find } from 'rxjs/operators';
import { hasValue } from '../../shared/empty.util';
import { followLink } from '../../shared/utils/follow-link-config.model'; import { followLink } from '../../shared/utils/follow-link-config.model';
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
/** /**
* This class represents a resolver that requests a specific collection's item template before the route is activated * This class represents a resolver that requests a specific collection's item template before the route is activated
@@ -24,8 +23,8 @@ export class ItemTemplatePageResolver implements Resolve<RemoteData<Item>> {
* or an error if something went wrong * or an error if something went wrong
*/ */
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> { resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> {
return this.itemTemplateService.findByCollectionID(route.params.id, followLink('templateItemOf')).pipe( return this.itemTemplateService.findByCollectionID(route.params.id, false, followLink('templateItemOf')).pipe(
find((RD) => hasValue(RD.error) || RD.hasSucceeded), getFirstCompletedRemoteData(),
); );
} }
} }

View File

@@ -1,8 +1,8 @@
import { mergeMap, filter, map } from 'rxjs/operators'; import { mergeMap, filter, map } from 'rxjs/operators';
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { Subscription, Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { CommunityDataService } from '../core/data/community-data.service'; import { CommunityDataService } from '../core/data/community-data.service';
import { RemoteData } from '../core/data/remote-data'; import { RemoteData } from '../core/data/remote-data';
import { Bitstream } from '../core/shared/bitstream.model'; import { Bitstream } from '../core/shared/bitstream.model';

View File

@@ -1,6 +1,6 @@
import { of as observableOf } from 'rxjs';
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
import { CommunityPageResolver } from './community-page.resolver'; import { CommunityPageResolver } from './community-page.resolver';
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
describe('CommunityPageResolver', () => { describe('CommunityPageResolver', () => {
describe('resolve', () => { describe('resolve', () => {
@@ -10,17 +10,18 @@ describe('CommunityPageResolver', () => {
beforeEach(() => { beforeEach(() => {
communityService = { communityService = {
findById: (id: string) => observableOf({ payload: { id }, hasSucceeded: true }) findById: (id: string) => createSuccessfulRemoteDataObject$({ id })
}; };
resolver = new CommunityPageResolver(communityService); resolver = new CommunityPageResolver(communityService);
}); });
it('should resolve a community with the correct id', () => { it('should resolve a community with the correct id', (done) => {
resolver.resolve({ params: { id: uuid } } as any, undefined) resolver.resolve({ params: { id: uuid } } as any, undefined)
.pipe(first()) .pipe(first())
.subscribe( .subscribe(
(resolved) => { (resolved) => {
expect(resolved.payload.id).toEqual(uuid); expect(resolved.payload.id).toEqual(uuid);
done();
} }
); );
}); });

View File

@@ -4,9 +4,8 @@ import { Observable } from 'rxjs';
import { RemoteData } from '../core/data/remote-data'; import { RemoteData } from '../core/data/remote-data';
import { Community } from '../core/shared/community.model'; import { Community } from '../core/shared/community.model';
import { CommunityDataService } from '../core/data/community-data.service'; import { CommunityDataService } from '../core/data/community-data.service';
import { find } from 'rxjs/operators';
import { hasValue } from '../shared/empty.util';
import { followLink } from '../shared/utils/follow-link-config.model'; import { followLink } from '../shared/utils/follow-link-config.model';
import { getFirstCompletedRemoteData } from '../core/shared/operators';
/** /**
* This class represents a resolver that requests a specific community before the route is activated * This class represents a resolver that requests a specific community before the route is activated
@@ -26,11 +25,12 @@ export class CommunityPageResolver implements Resolve<RemoteData<Community>> {
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Community>> { resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Community>> {
return this.communityService.findById( return this.communityService.findById(
route.params.id, route.params.id,
false,
followLink('logo'), followLink('logo'),
followLink('subcommunities'), followLink('subcommunities'),
followLink('collections') followLink('collections')
).pipe( ).pipe(
find((RD) => hasValue(RD.error) || RD.hasSucceeded) getFirstCompletedRemoteData(),
); );
} }
} }

View File

@@ -2,7 +2,10 @@ import { CreateCommunityPageGuard } from './create-community-page.guard';
import { RouterMock } from '../../shared/mocks/router.mock'; import { RouterMock } from '../../shared/mocks/router.mock';
import { Community } from '../../core/shared/community.model'; import { Community } from '../../core/shared/community.model';
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; import {
createFailedRemoteDataObject$,
createSuccessfulRemoteDataObject$
} from '../../shared/remote-data.utils';
describe('CreateCommunityPageGuard', () => { describe('CreateCommunityPageGuard', () => {
describe('canActivate', () => { describe('canActivate', () => {
@@ -18,7 +21,7 @@ describe('CreateCommunityPageGuard', () => {
} else if (id === 'invalid-id') { } else if (id === 'invalid-id') {
return createSuccessfulRemoteDataObject$(undefined); return createSuccessfulRemoteDataObject$(undefined);
} else if (id === 'error-id') { } else if (id === 'error-id') {
return createFailedRemoteDataObject$(new Community()); return createFailedRemoteDataObject$('not found', 404);
} }
} }
}; };

View File

@@ -5,8 +5,9 @@ import { hasNoValue, hasValue } from '../../shared/empty.util';
import { CommunityDataService } from '../../core/data/community-data.service'; import { CommunityDataService } from '../../core/data/community-data.service';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { Community } from '../../core/shared/community.model'; import { Community } from '../../core/shared/community.model';
import { map, tap, find } from 'rxjs/operators'; import { map, tap } from 'rxjs/operators';
import { Observable, of as observableOf } from 'rxjs'; import { Observable, of as observableOf } from 'rxjs';
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
/** /**
* Prevent creation of a community with an invalid parent community provided * Prevent creation of a community with an invalid parent community provided
@@ -30,7 +31,7 @@ export class CreateCommunityPageGuard implements CanActivate {
return this.communityService.findById(parentID) return this.communityService.findById(parentID)
.pipe( .pipe(
find((communityRD: RemoteData<Community>) => hasValue(communityRD.payload) || hasValue(communityRD.error)), getFirstCompletedRemoteData(),
map((communityRD: RemoteData<Community>) => hasValue(communityRD) && communityRD.hasSucceeded && hasValue(communityRD.payload)), map((communityRD: RemoteData<Community>) => hasValue(communityRD) && communityRD.hasSucceeded && hasValue(communityRD.payload)),
tap((isValid: boolean) => { tap((isValid: boolean) => {
if (!isValid) { if (!isValid) {

View File

@@ -11,6 +11,7 @@ import { RequestService } from '../../../core/data/request.service';
import { GroupDataService } from '../../../core/eperson/group-data.service'; import { GroupDataService } from '../../../core/eperson/group-data.service';
import { SharedModule } from '../../../shared/shared.module'; import { SharedModule } from '../../../shared/shared.module';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
describe('CommunityRolesComponent', () => { describe('CommunityRolesComponent', () => {
@@ -23,11 +24,7 @@ describe('CommunityRolesComponent', () => {
const route = { const route = {
parent: { parent: {
data: observableOf({ data: observableOf({
dso: new RemoteData( dso: createSuccessfulRemoteDataObject(
false,
false,
true,
undefined,
Object.assign(new Community(), { Object.assign(new Community(), {
_links: { _links: {
irrelevant: { irrelevant: {
@@ -37,25 +34,18 @@ describe('CommunityRolesComponent', () => {
href: 'adminGroup link', href: 'adminGroup link',
}, },
}, },
}), })
), ),
}) })
} }
}; };
const requestService = { const requestService = {
hasByHrefObservable: () => observableOf(true), hasByHref$: () => observableOf(true),
}; };
const groupDataService = { const groupDataService = {
findByHref: () => observableOf(new RemoteData( findByHref: () => createSuccessfulRemoteDataObject$({}),
false,
false,
true,
undefined,
{},
200,
)),
}; };
TestBed.configureTestingModule({ TestBed.configureTestingModule({

View File

@@ -3,7 +3,7 @@ import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { first, map } from 'rxjs/operators'; import { first, map } from 'rxjs/operators';
import { Community } from '../../../core/shared/community.model'; import { Community } from '../../../core/shared/community.model';
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../core/shared/operators'; import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../../../core/shared/operators';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { HALLink } from '../../../core/shared/hal-link.model'; import { HALLink } from '../../../core/shared/hal-link.model';
@@ -23,7 +23,7 @@ export class CommunityRolesComponent implements OnInit {
*/ */
get community$(): Observable<Community> { get community$(): Observable<Community> {
return this.dsoRD$.pipe( return this.dsoRD$.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
) )
} }

View File

@@ -13,7 +13,7 @@ import { SharedModule } from '../../shared/shared.module';
import { CollectionDataService } from '../../core/data/collection-data.service'; import { CollectionDataService } from '../../core/data/collection-data.service';
import { FindListOptions } from '../../core/data/request.models'; import { FindListOptions } from '../../core/data/request.models';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
import { PaginatedList } from '../../core/data/paginated-list'; import { buildPaginatedList } from '../../core/data/paginated-list.model';
import { PageInfo } from '../../core/shared/page-info.model'; import { PageInfo } from '../../core/shared/page-info.model';
import { HostWindowService } from '../../shared/host-window.service'; import { HostWindowService } from '../../shared/host-window.service';
import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub'; import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub';
@@ -105,7 +105,7 @@ describe('CommunityPageSubCollectionList Component', () => {
if (endPageIndex > subCollList.length) { if (endPageIndex > subCollList.length) {
endPageIndex = subCollList.length; endPageIndex = subCollList.length;
} }
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), subCollList.slice(startPageIndex, endPageIndex))); return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), subCollList.slice(startPageIndex, endPageIndex)));
} }
}; };

View File

@@ -1,16 +1,16 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { take } from 'rxjs/operators';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { Collection } from '../../core/shared/collection.model'; import { Collection } from '../../core/shared/collection.model';
import { Community } from '../../core/shared/community.model'; import { Community } from '../../core/shared/community.model';
import { fadeIn } from '../../shared/animations/fade'; import { fadeIn } from '../../shared/animations/fade';
import { PaginatedList } from '../../core/data/paginated-list'; import { PaginatedList } from '../../core/data/paginated-list.model';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { CollectionDataService } from '../../core/data/collection-data.service'; import { CollectionDataService } from '../../core/data/collection-data.service';
import { takeUntilCompletedRemoteData } from '../../core/shared/operators';
@Component({ @Component({
selector: 'ds-community-page-sub-collection-list', selector: 'ds-community-page-sub-collection-list',
@@ -72,7 +72,7 @@ export class CommunityPageSubCollectionListComponent implements OnInit {
currentPage: this.config.currentPage, currentPage: this.config.currentPage,
elementsPerPage: this.config.pageSize, elementsPerPage: this.config.pageSize,
sort: { field: this.sortConfig.field, direction: this.sortConfig.direction } sort: { field: this.sortConfig.field, direction: this.sortConfig.direction }
}).pipe(take(1)).subscribe((results) => { }).pipe(takeUntilCompletedRemoteData()).subscribe((results) => {
this.subCollectionsRDObs.next(results); this.subCollectionsRDObs.next(results);
}); });
} }

View File

@@ -9,7 +9,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { CommunityPageSubCommunityListComponent } from './community-page-sub-community-list.component'; import { CommunityPageSubCommunityListComponent } from './community-page-sub-community-list.component';
import { Community } from '../../core/shared/community.model'; import { Community } from '../../core/shared/community.model';
import { PaginatedList } from '../../core/data/paginated-list'; import { PaginatedList, buildPaginatedList } from '../../core/data/paginated-list.model';
import { PageInfo } from '../../core/shared/page-info.model'; import { PageInfo } from '../../core/shared/page-info.model';
import { SharedModule } from '../../shared/shared.module'; import { SharedModule } from '../../shared/shared.module';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
@@ -106,7 +106,7 @@ describe('CommunityPageSubCommunityListComponent Component', () => {
if (endPageIndex > subCommList.length) { if (endPageIndex > subCommList.length) {
endPageIndex = subCommList.length; endPageIndex = subCommList.length;
} }
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), subCommList.slice(startPageIndex, endPageIndex))); return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), subCommList.slice(startPageIndex, endPageIndex)));
} }
}; };

View File

@@ -1,15 +1,15 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { take } from 'rxjs/operators';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { Community } from '../../core/shared/community.model'; import { Community } from '../../core/shared/community.model';
import { fadeIn } from '../../shared/animations/fade'; import { fadeIn } from '../../shared/animations/fade';
import { PaginatedList } from '../../core/data/paginated-list'; import { PaginatedList } from '../../core/data/paginated-list.model';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { CommunityDataService } from '../../core/data/community-data.service'; import { CommunityDataService } from '../../core/data/community-data.service';
import { takeUntilCompletedRemoteData } from '../../core/shared/operators';
@Component({ @Component({
selector: 'ds-community-page-sub-community-list', selector: 'ds-community-page-sub-community-list',
@@ -75,7 +75,7 @@ export class CommunityPageSubCommunityListComponent implements OnInit {
currentPage: this.config.currentPage, currentPage: this.config.currentPage,
elementsPerPage: this.config.pageSize, elementsPerPage: this.config.pageSize,
sort: { field: this.sortConfig.field, direction: this.sortConfig.direction } sort: { field: this.sortConfig.field, direction: this.sortConfig.direction }
}).pipe(take(1)).subscribe((results) => { }).pipe(takeUntilCompletedRemoteData()).subscribe((results) => {
this.subCommunitiesRDObs.next(results); this.subCommunitiesRDObs.next(results);
}); });
} }

View File

@@ -9,7 +9,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TopLevelCommunityListComponent } from './top-level-community-list.component'; import { TopLevelCommunityListComponent } from './top-level-community-list.component';
import { Community } from '../../core/shared/community.model'; import { Community } from '../../core/shared/community.model';
import { PaginatedList } from '../../core/data/paginated-list'; import { PaginatedList, buildPaginatedList } from '../../core/data/paginated-list.model';
import { PageInfo } from '../../core/shared/page-info.model'; import { PageInfo } from '../../core/shared/page-info.model';
import { SharedModule } from '../../shared/shared.module'; import { SharedModule } from '../../shared/shared.module';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
@@ -96,7 +96,7 @@ describe('TopLevelCommunityList Component', () => {
if (endPageIndex > topCommList.length) { if (endPageIndex > topCommList.length) {
endPageIndex = topCommList.length; endPageIndex = topCommList.length;
} }
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), topCommList.slice(startPageIndex, endPageIndex))); return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), topCommList.slice(startPageIndex, endPageIndex)));
} }
}; };

View File

@@ -1,15 +1,15 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, OnInit, OnDestroy } from '@angular/core';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject, Subscription } from 'rxjs';
import { take } from 'rxjs/operators';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { CommunityDataService } from '../../core/data/community-data.service'; import { CommunityDataService } from '../../core/data/community-data.service';
import { PaginatedList } from '../../core/data/paginated-list'; import { PaginatedList } from '../../core/data/paginated-list.model';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { Community } from '../../core/shared/community.model'; import { Community } from '../../core/shared/community.model';
import { fadeInOut } from '../../shared/animations/fade'; import { fadeInOut } from '../../shared/animations/fade';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { hasValue } from '../../shared/empty.util';
/** /**
* this component renders the Top-Level Community list * this component renders the Top-Level Community list
@@ -22,7 +22,7 @@ import { PaginationComponentOptions } from '../../shared/pagination/pagination-c
animations: [fadeInOut] animations: [fadeInOut]
}) })
export class TopLevelCommunityListComponent implements OnInit { export class TopLevelCommunityListComponent implements OnInit, OnDestroy {
/** /**
* A list of remote data objects of all top communities * A list of remote data objects of all top communities
*/ */
@@ -43,6 +43,11 @@ export class TopLevelCommunityListComponent implements OnInit {
*/ */
sortConfig: SortOptions; sortConfig: SortOptions;
/**
* The subscription to the observable for the current page.
*/
currentPageSubscription: Subscription;
constructor(private cds: CommunityDataService) { constructor(private cds: CommunityDataService) {
this.config = new PaginationComponentOptions(); this.config = new PaginationComponentOptions();
this.config.id = this.pageId; this.config.id = this.pageId;
@@ -71,12 +76,29 @@ export class TopLevelCommunityListComponent implements OnInit {
* Update the list of top communities * Update the list of top communities
*/ */
updatePage() { updatePage() {
this.cds.findTop({ this.unsubscribe();
this.currentPageSubscription = this.cds.findTop({
currentPage: this.config.currentPage, currentPage: this.config.currentPage,
elementsPerPage: this.config.pageSize, elementsPerPage: this.config.pageSize,
sort: { field: this.sortConfig.field, direction: this.sortConfig.direction } sort: { field: this.sortConfig.field, direction: this.sortConfig.direction }
}).pipe(take(1)).subscribe((results) => { }).subscribe((results) => {
this.communitiesRD$.next(results); this.communitiesRD$.next(results);
}); });
} }
/**
* Unsubscribe the top list subscription if it exists
*/
private unsubscribe() {
if (hasValue(this.currentPageSubscription)) {
this.currentPageSubscription.unsubscribe();
}
}
/**
* Clean up subscriptions when the component is destroyed
*/
ngOnDestroy() {
this.unsubscribe();
}
} }

View File

@@ -16,7 +16,10 @@ import { Bitstream } from '../../../core/shared/bitstream.model';
import { BundleDataService } from '../../../core/data/bundle-data.service'; import { BundleDataService } from '../../../core/data/bundle-data.service';
import { Bundle } from '../../../core/shared/bundle.model'; import { Bundle } from '../../../core/shared/bundle.model';
import { RequestService } from '../../../core/data/request.service'; import { RequestService } from '../../../core/data/request.service';
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; import {
createSuccessfulRemoteDataObject,
createSuccessfulRemoteDataObject$
} from '../../../shared/remote-data.utils';
import { createPaginatedList } from '../../../shared/testing/utils.test'; import { createPaginatedList } from '../../../shared/testing/utils.test';
import { RouterStub } from '../../../shared/testing/router.stub'; import { RouterStub } from '../../../shared/testing/router.stub';
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
@@ -192,7 +195,7 @@ describe('UploadBistreamComponent', () => {
function createUploadBitstreamTestingModule(queryParams) { function createUploadBitstreamTestingModule(queryParams) {
routeStub = { routeStub = {
data: observableOf({ data: observableOf({
item: createSuccessfulRemoteDataObject(mockItem) dso: createSuccessfulRemoteDataObject(mockItem)
}), }),
queryParams: observableOf(queryParams), queryParams: observableOf(queryParams),
snapshot: { snapshot: {

View File

@@ -11,7 +11,7 @@ import { ItemDataService } from '../../../core/data/item-data.service';
import { AuthService } from '../../../core/auth/auth.service'; import { AuthService } from '../../../core/auth/auth.service';
import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginatedList } from '../../../core/data/paginated-list.model';
import { Bundle } from '../../../core/shared/bundle.model'; import { Bundle } from '../../../core/shared/bundle.model';
import { BundleDataService } from '../../../core/data/bundle-data.service'; import { BundleDataService } from '../../../core/data/bundle-data.service';
import { import {
@@ -103,7 +103,7 @@ export class UploadBitstreamComponent implements OnInit, OnDestroy {
*/ */
ngOnInit(): void { ngOnInit(): void {
this.itemId = this.route.snapshot.params.id; this.itemId = this.route.snapshot.params.id;
this.itemRD$ = this.route.data.pipe(map((data) => data.item)); this.itemRD$ = this.route.data.pipe(map((data) => data.dso));
this.bundlesRD$ = this.itemRD$.pipe( this.bundlesRD$ = this.itemRD$.pipe(
switchMap((itemRD: RemoteData<Item>) => itemRD.payload.bundles) switchMap((itemRD: RemoteData<Item>) => itemRD.payload.bundles)
); );

View File

@@ -6,6 +6,7 @@ import {
createFailedRemoteDataObject, createFailedRemoteDataObject,
createSuccessfulRemoteDataObject createSuccessfulRemoteDataObject
} from '../../shared/remote-data.utils'; } from '../../shared/remote-data.utils';
import { isNotEmpty } from '../../shared/empty.util';
describe('findSuccessfulAccordingTo', () => { describe('findSuccessfulAccordingTo', () => {
let mockItem1; let mockItem1;
@@ -19,12 +20,12 @@ describe('findSuccessfulAccordingTo', () => {
mockItem2 = new Item(); mockItem2 = new Item();
mockItem1.isWithdrawn = false; mockItem1.isWithdrawn = false;
predicate = (rd: RemoteData<Item>) => rd.payload.isWithdrawn; predicate = (rd: RemoteData<Item>) => isNotEmpty(rd.payload) ? rd.payload.isWithdrawn : false;
}); });
it('should return first successful RemoteData Observable that complies to predicate', () => { it('should return first successful RemoteData Observable that complies to predicate', () => {
const testRD = { const testRD = {
a: createSuccessfulRemoteDataObject(undefined), a: createSuccessfulRemoteDataObject(undefined),
b: createFailedRemoteDataObject(mockItem1), b: createFailedRemoteDataObject(),
c: createSuccessfulRemoteDataObject(mockItem2), c: createSuccessfulRemoteDataObject(mockItem2),
d: createSuccessfulRemoteDataObject(mockItem1), d: createSuccessfulRemoteDataObject(mockItem1),
e: createSuccessfulRemoteDataObject(mockItem2), e: createSuccessfulRemoteDataObject(mockItem2),

View File

@@ -2,21 +2,18 @@ import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing'
import { Component, NO_ERRORS_SCHEMA } from '@angular/core'; import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { cold } from 'jasmine-marbles'; import { cold } from 'jasmine-marbles';
import { ItemAuthorizationsComponent } from './item-authorizations.component'; import { ItemAuthorizationsComponent } from './item-authorizations.component';
import { Bitstream } from '../../../core/shared/bitstream.model'; import { Bitstream } from '../../../core/shared/bitstream.model';
import { Bundle } from '../../../core/shared/bundle.model'; import { Bundle } from '../../../core/shared/bundle.model';
import { createMockRDPaginatedObs } from '../item-bitstreams/item-bitstreams.component.spec';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { LinkService } from '../../../core/cache/builders/link.service'; import { LinkService } from '../../../core/cache/builders/link.service';
import { getMockLinkService } from '../../../shared/mocks/link-service.mock'; import { getMockLinkService } from '../../../shared/mocks/link-service.mock';
import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils'; import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
import { createTestComponent } from '../../../shared/testing/utils.test'; import { createPaginatedList, createTestComponent } from '../../../shared/testing/utils.test';
import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginatedList, buildPaginatedList } from '../../../core/data/paginated-list.model';
import { PageInfo } from '../../../core/shared/page-info.model'; import { PageInfo } from '../../../core/shared/page-info.model';
describe('ItemAuthorizationsComponent test suite', () => { describe('ItemAuthorizationsComponent test suite', () => {
@@ -49,7 +46,7 @@ describe('ItemAuthorizationsComponent test suite', () => {
_links: { _links: {
self: { href: 'bundle1-selflink' } self: { href: 'bundle1-selflink' }
}, },
bitstreams: createMockRDPaginatedObs([bitstream1, bitstream2]) bitstreams: createSuccessfulRemoteDataObject$(createPaginatedList([bitstream1, bitstream2]))
}); });
const bundle2 = Object.assign(new Bundle(), { const bundle2 = Object.assign(new Bundle(), {
id: 'bundle2', id: 'bundle2',
@@ -57,11 +54,11 @@ describe('ItemAuthorizationsComponent test suite', () => {
_links: { _links: {
self: { href: 'bundle2-selflink' } self: { href: 'bundle2-selflink' }
}, },
bitstreams: createMockRDPaginatedObs([bitstream3, bitstream4]) bitstreams: createSuccessfulRemoteDataObject$(createPaginatedList([bitstream3, bitstream4]))
}); });
const bundles = [bundle1, bundle2]; const bundles = [bundle1, bundle2];
const bitstreamList1: PaginatedList<Bitstream> = new PaginatedList(new PageInfo(), [bitstream1, bitstream2]); const bitstreamList1: PaginatedList<Bitstream> = buildPaginatedList(new PageInfo(), [bitstream1, bitstream2]);
const bitstreamList2: PaginatedList<Bitstream> = new PaginatedList(new PageInfo(), [bitstream3, bitstream4]); const bitstreamList2: PaginatedList<Bitstream> = buildPaginatedList(new PageInfo(), [bitstream3, bitstream4]);
const item = Object.assign(new Item(), { const item = Object.assign(new Item(), {
uuid: 'item', uuid: 'item',
@@ -69,7 +66,7 @@ describe('ItemAuthorizationsComponent test suite', () => {
_links: { _links: {
self: { href: 'item-selflink' } self: { href: 'item-selflink' }
}, },
bundles: createMockRDPaginatedObs([bundle1, bundle2]) bundles: createSuccessfulRemoteDataObject$(createPaginatedList([bundle1, bundle2]))
}); });
const routeStub = { const routeStub = {

View File

@@ -4,7 +4,7 @@ import { ActivatedRoute } from '@angular/router';
import { BehaviorSubject, Observable, of as observableOf, Subscription } from 'rxjs'; import { BehaviorSubject, Observable, of as observableOf, Subscription } from 'rxjs';
import { catchError, filter, first, flatMap, map, take } from 'rxjs/operators'; import { catchError, filter, first, flatMap, map, take } from 'rxjs/operators';
import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginatedList, buildPaginatedList } from '../../../core/data/paginated-list.model';
import { import {
getFirstSucceededRemoteDataPayload, getFirstSucceededRemoteDataPayload,
getFirstSucceededRemoteDataWithNotEmptyPayload getFirstSucceededRemoteDataWithNotEmptyPayload
@@ -89,7 +89,7 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
getFirstSucceededRemoteDataWithNotEmptyPayload(), getFirstSucceededRemoteDataWithNotEmptyPayload(),
catchError((error) => { catchError((error) => {
console.error(error); console.error(error);
return observableOf(new PaginatedList(null, [])) return observableOf(buildPaginatedList(null, []))
}) })
); );
@@ -139,7 +139,7 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
getFirstSucceededRemoteDataPayload(), getFirstSucceededRemoteDataPayload(),
catchError((error) => { catchError((error) => {
console.error(error); console.error(error);
return observableOf(new PaginatedList(null, [])) return observableOf(buildPaginatedList(null, []))
}) })
) )
} }

View File

@@ -1,8 +1,5 @@
import { Bitstream } from '../../../core/shared/bitstream.model'; import { Bitstream } from '../../../core/shared/bitstream.model';
import { of as observableOf } from 'rxjs/internal/observable/of'; import { of as observableOf } from 'rxjs/internal/observable/of';
import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list';
import { PageInfo } from '../../../core/shared/page-info.model';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ItemBitstreamsComponent } from './item-bitstreams.component'; import { ItemBitstreamsComponent } from './item-bitstreams.component';
@@ -13,7 +10,10 @@ import { ActivatedRoute, Router } from '@angular/router';
import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { ChangeDetectorRef, NO_ERRORS_SCHEMA } from '@angular/core'; import { ChangeDetectorRef, NO_ERRORS_SCHEMA } from '@angular/core';
import { FieldChangeType } from '../../../core/data/object-updates/object-updates.actions'; import { FieldChangeType } from '../../../core/data/object-updates/object-updates.actions';
import { INotification, Notification } from '../../../shared/notifications/models/notification.model'; import {
INotification,
Notification
} from '../../../shared/notifications/models/notification.model';
import { NotificationType } from '../../../shared/notifications/models/notification-type'; import { NotificationType } from '../../../shared/notifications/models/notification-type';
import { BitstreamDataService } from '../../../core/data/bitstream-data.service'; import { BitstreamDataService } from '../../../core/data/bitstream-data.service';
import { ObjectCacheService } from '../../../core/cache/object-cache.service'; import { ObjectCacheService } from '../../../core/cache/object-cache.service';
@@ -26,6 +26,11 @@ import { RestResponse } from '../../../core/cache/response.models';
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
import { RouterStub } from '../../../shared/testing/router.stub'; import { RouterStub } from '../../../shared/testing/router.stub';
import { getMockRequestService } from '../../../shared/mocks/request.service.mock'; import { getMockRequestService } from '../../../shared/mocks/request.service.mock';
import {
createSuccessfulRemoteDataObject,
createSuccessfulRemoteDataObject$
} from '../../../shared/remote-data.utils';
import { createPaginatedList } from '../../../shared/testing/utils.test';
let comp: ItemBitstreamsComponent; let comp: ItemBitstreamsComponent;
let fixture: ComponentFixture<ItemBitstreamsComponent>; let fixture: ComponentFixture<ItemBitstreamsComponent>;
@@ -55,7 +60,7 @@ const bundle = Object.assign(new Bundle(), {
_links: { _links: {
self: { href: 'bundle1-selflink' } self: { href: 'bundle1-selflink' }
}, },
bitstreams: createMockRDPaginatedObs([bitstream1, bitstream2]) bitstreams: createSuccessfulRemoteDataObject$(createPaginatedList([bitstream1, bitstream2]))
}); });
const moveOperations = [ const moveOperations = [
{ {
@@ -130,17 +135,17 @@ describe('ItemBitstreamsComponent', () => {
_links: { _links: {
self: { href: 'item-selflink' } self: { href: 'item-selflink' }
}, },
bundles: createMockRDPaginatedObs([bundle]), bundles: createSuccessfulRemoteDataObject$(createPaginatedList([bundle])),
lastModified: date lastModified: date
}); });
itemService = Object.assign( { itemService = Object.assign( {
getBitstreams: () => createMockRDPaginatedObs([bitstream1, bitstream2]), getBitstreams: () => createSuccessfulRemoteDataObject$(createPaginatedList([bitstream1, bitstream2])),
findById: () => createMockRDObs(item), findById: () => createSuccessfulRemoteDataObject$(item),
getBundles: () => createMockRDPaginatedObs([bundle]) getBundles: () => createSuccessfulRemoteDataObject$(createPaginatedList([bundle]))
}); });
route = Object.assign({ route = Object.assign({
parent: { parent: {
data: observableOf({ dso: createMockRD(item) }) data: observableOf({ dso: createSuccessfulRemoteDataObject(item) })
}, },
data: observableOf({}), data: observableOf({}),
url: url url: url
@@ -235,15 +240,3 @@ describe('ItemBitstreamsComponent', () => {
}); });
}); });
}); });
export function createMockRDPaginatedObs(list: any[]) {
return createMockRDObs(new PaginatedList(new PageInfo(), list));
}
export function createMockRDObs(obj: any) {
return observableOf(createMockRD(obj));
}
export function createMockRD(obj: any) {
return new RemoteData(false, false, true, null, obj);
}

View File

@@ -11,13 +11,12 @@ import { TranslateService } from '@ngx-translate/core';
import { BitstreamDataService } from '../../../core/data/bitstream-data.service'; import { BitstreamDataService } from '../../../core/data/bitstream-data.service';
import { hasValue, isNotEmpty } from '../../../shared/empty.util'; import { hasValue, isNotEmpty } from '../../../shared/empty.util';
import { zip as observableZip, of as observableOf } from 'rxjs'; import { zip as observableZip, of as observableOf } from 'rxjs';
import { ErrorResponse, RestResponse } from '../../../core/cache/response.models';
import { ObjectCacheService } from '../../../core/cache/object-cache.service'; import { ObjectCacheService } from '../../../core/cache/object-cache.service';
import { RequestService } from '../../../core/data/request.service'; import { RequestService } from '../../../core/data/request.service';
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../core/shared/operators'; import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../../../core/shared/operators';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginatedList } from '../../../core/data/paginated-list.model';
import { Bundle } from '../../../core/shared/bundle.model'; import { Bundle } from '../../../core/shared/bundle.model';
import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer'; import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer';
import { Bitstream } from '../../../core/shared/bitstream.model'; import { Bitstream } from '../../../core/shared/bitstream.model';
@@ -26,6 +25,8 @@ import { BundleDataService } from '../../../core/data/bundle-data.service';
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model'; import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
import { ResponsiveColumnSizes } from '../../../shared/responsive-table-sizes/responsive-column-sizes'; import { ResponsiveColumnSizes } from '../../../shared/responsive-table-sizes/responsive-column-sizes';
import { ResponsiveTableSizes } from '../../../shared/responsive-table-sizes/responsive-table-sizes'; import { ResponsiveTableSizes } from '../../../shared/responsive-table-sizes/responsive-table-sizes';
import { NoContent } from '../../../core/shared/NoContent.model';
import { Operation } from 'fast-json-patch';
@Component({ @Component({
selector: 'ds-item-bitstreams', selector: 'ds-item-bitstreams',
@@ -107,7 +108,7 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
*/ */
postItemInit(): void { postItemInit(): void {
this.bundles$ = this.itemService.getBundles(this.item.id, new PaginatedSearchOptions({pagination: this.bundlesOptions})).pipe( this.bundles$ = this.itemService.getBundles(this.item.id, new PaginatedSearchOptions({pagination: this.bundlesOptions})).pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
map((bundlePage: PaginatedList<Bundle>) => bundlePage.page) map((bundlePage: PaginatedList<Bundle>) => bundlePage.page)
); );
@@ -125,10 +126,10 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
* Also re-initialize the original fields and updates * Also re-initialize the original fields and updates
*/ */
initializeItemUpdate(): void { initializeItemUpdate(): void {
this.itemUpdateSubscription = this.requestService.hasByHrefObservable(this.item.self).pipe( this.itemUpdateSubscription = this.requestService.hasByHref$(this.item.self).pipe(
filter((exists: boolean) => !exists), filter((exists: boolean) => !exists),
switchMap(() => this.itemService.findById(this.item.uuid)), switchMap(() => this.itemService.findById(this.item.uuid)),
getSucceededRemoteData(), getFirstSucceededRemoteData(),
).subscribe((itemRD: RemoteData<Item>) => { ).subscribe((itemRD: RemoteData<Item>) => {
if (hasValue(itemRD)) { if (hasValue(itemRD)) {
this.item = itemRD.payload; this.item = itemRD.payload;
@@ -173,7 +174,7 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
); );
// Perform the setup actions from above in order and display notifications // Perform the setup actions from above in order and display notifications
removedResponses$.pipe(take(1)).subscribe((responses: RestResponse[]) => { removedResponses$.pipe(take(1)).subscribe((responses: Array<RemoteData<NoContent>>) => {
this.displayNotifications('item.edit.bitstreams.notifications.remove', responses); this.displayNotifications('item.edit.bitstreams.notifications.remove', responses);
this.reset(); this.reset();
this.submitting = false; this.submitting = false;
@@ -190,12 +191,12 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
dropBitstream(bundle: Bundle, event: any) { dropBitstream(bundle: Bundle, event: any) {
this.zone.runOutsideAngular(() => { this.zone.runOutsideAngular(() => {
if (hasValue(event) && hasValue(event.fromIndex) && hasValue(event.toIndex) && hasValue(event.finish)) { if (hasValue(event) && hasValue(event.fromIndex) && hasValue(event.toIndex) && hasValue(event.finish)) {
const moveOperation = Object.assign({ const moveOperation = {
op: 'move', op: 'move',
from: `/_links/bitstreams/${event.fromIndex}/href`, from: `/_links/bitstreams/${event.fromIndex}/href`,
path: `/_links/bitstreams/${event.toIndex}/href` path: `/_links/bitstreams/${event.toIndex}/href`
}); } as Operation;
this.bundleService.patch(bundle, [moveOperation]).pipe(take(1)).subscribe((response: RestResponse) => { this.bundleService.patch(bundle, [moveOperation]).pipe(take(1)).subscribe((response: RemoteData<Bundle>) => {
this.zone.run(() => { this.zone.run(() => {
this.displayNotifications('item.edit.bitstreams.notifications.move', [response]); this.displayNotifications('item.edit.bitstreams.notifications.move', [response]);
// Remove all cached requests from this bundle and call the event's callback when the requests are cleared // Remove all cached requests from this bundle and call the event's callback when the requests are cleared
@@ -216,12 +217,12 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
* @param key The i18n key for the notification messages * @param key The i18n key for the notification messages
* @param responses The returned responses to display notifications for * @param responses The returned responses to display notifications for
*/ */
displayNotifications(key: string, responses: RestResponse[]) { displayNotifications(key: string, responses: Array<RemoteData<any>>) {
if (isNotEmpty(responses)) { if (isNotEmpty(responses)) {
const failedResponses = responses.filter((response: RestResponse) => hasValue(response) && !response.isSuccessful); const failedResponses = responses.filter((response: RemoteData<Bundle>) => hasValue(response) && response.hasFailed);
const successfulResponses = responses.filter((response: RestResponse) => hasValue(response) && response.isSuccessful); const successfulResponses = responses.filter((response: RemoteData<Bundle>) => hasValue(response) && response.hasSucceeded);
failedResponses.forEach((response: ErrorResponse) => { failedResponses.forEach((response: RemoteData<Bundle>) => {
this.notificationsService.error(this.translateService.instant(`${key}.failed.title`), response.errorMessage); this.notificationsService.error(this.translateService.instant(`${key}.failed.title`), response.errorMessage);
}); });
if (successfulResponses.length > 0) { if (successfulResponses.length > 0) {

View File

@@ -7,7 +7,6 @@ import { VarDirective } from '../../../../../shared/utils/var.directive';
import { ObjectValuesPipe } from '../../../../../shared/utils/object-values-pipe'; import { ObjectValuesPipe } from '../../../../../shared/utils/object-values-pipe';
import { ObjectUpdatesService } from '../../../../../core/data/object-updates/object-updates.service'; import { ObjectUpdatesService } from '../../../../../core/data/object-updates/object-updates.service';
import { BundleDataService } from '../../../../../core/data/bundle-data.service'; import { BundleDataService } from '../../../../../core/data/bundle-data.service';
import { createMockRDObs } from '../../item-bitstreams.component.spec';
import { Bitstream } from '../../../../../core/shared/bitstream.model'; import { Bitstream } from '../../../../../core/shared/bitstream.model';
import { BitstreamFormat } from '../../../../../core/shared/bitstream-format.model'; import { BitstreamFormat } from '../../../../../core/shared/bitstream-format.model';
import { of as observableOf } from 'rxjs/internal/observable/of'; import { of as observableOf } from 'rxjs/internal/observable/of';
@@ -49,7 +48,7 @@ describe('PaginatedDragAndDropBitstreamListComponent', () => {
name: 'Fake Bitstream 1', name: 'Fake Bitstream 1',
bundleName: 'ORIGINAL', bundleName: 'ORIGINAL',
description: 'Description', description: 'Description',
format: createMockRDObs(format) format: createSuccessfulRemoteDataObject$(format)
}); });
const fieldUpdate1 = { const fieldUpdate1 = {
field: bitstream1, field: bitstream1,
@@ -60,7 +59,7 @@ describe('PaginatedDragAndDropBitstreamListComponent', () => {
name: 'Fake Bitstream 2', name: 'Fake Bitstream 2',
bundleName: 'ORIGINAL', bundleName: 'ORIGINAL',
description: 'Description', description: 'Description',
format: createMockRDObs(format) format: createSuccessfulRemoteDataObject$(format)
}); });
const fieldUpdate2 = { const fieldUpdate2 = {
field: bitstream2, field: bitstream2,
@@ -107,7 +106,7 @@ describe('PaginatedDragAndDropBitstreamListComponent', () => {
objectValuesPipe = new ObjectValuesPipe(); objectValuesPipe = new ObjectValuesPipe();
requestService = jasmine.createSpyObj('requestService', { requestService = jasmine.createSpyObj('requestService', {
hasByHrefObservable: observableOf(true) hasByHref$: observableOf(true)
}); });
TestBed.configureTestingModule({ TestBed.configureTestingModule({

View File

@@ -53,7 +53,7 @@ export class PaginatedDragAndDropBitstreamListComponent extends AbstractPaginate
switchMap((page: number) => { switchMap((page: number) => {
const paginatedOptions = new PaginatedSearchOptions({pagination: Object.assign({}, this.options, { currentPage: page })}); const paginatedOptions = new PaginatedSearchOptions({pagination: Object.assign({}, this.options, { currentPage: page })});
return this.bundleService.getBitstreamsEndpoint(this.bundle.id, paginatedOptions).pipe( return this.bundleService.getBitstreamsEndpoint(this.bundle.id, paginatedOptions).pipe(
switchMap((href) => this.requestService.hasByHrefObservable(href)), switchMap((href) => this.requestService.hasByHref$(href)),
switchMap(() => this.bundleService.getBitstreams( switchMap(() => this.bundleService.getBitstreams(
this.bundle.id, this.bundle.id,
paginatedOptions, paginatedOptions,

View File

@@ -6,10 +6,10 @@ import { Bitstream } from '../../../../core/shared/bitstream.model';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { VarDirective } from '../../../../shared/utils/var.directive'; import { VarDirective } from '../../../../shared/utils/var.directive';
import { NO_ERRORS_SCHEMA } from '@angular/core'; import { NO_ERRORS_SCHEMA } from '@angular/core';
import { createMockRDObs } from '../item-bitstreams.component.spec';
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model'; import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
import { ResponsiveTableSizes } from '../../../../shared/responsive-table-sizes/responsive-table-sizes'; import { ResponsiveTableSizes } from '../../../../shared/responsive-table-sizes/responsive-table-sizes';
import { ResponsiveColumnSizes } from '../../../../shared/responsive-table-sizes/responsive-column-sizes'; import { ResponsiveColumnSizes } from '../../../../shared/responsive-table-sizes/responsive-column-sizes';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
let comp: ItemEditBitstreamComponent; let comp: ItemEditBitstreamComponent;
let fixture: ComponentFixture<ItemEditBitstreamComponent>; let fixture: ComponentFixture<ItemEditBitstreamComponent>;
@@ -29,7 +29,7 @@ const bitstream = Object.assign(new Bitstream(), {
name: 'Fake Bitstream', name: 'Fake Bitstream',
bundleName: 'ORIGINAL', bundleName: 'ORIGINAL',
description: 'Description', description: 'Description',
format: createMockRDObs(format) format: createSuccessfulRemoteDataObject$(format)
}); });
const fieldUpdate = { const fieldUpdate = {
field: bitstream, field: bitstream,

View File

@@ -6,7 +6,7 @@ import { ObjectUpdatesService } from '../../../../core/data/object-updates/objec
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions'; import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model'; import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators'; import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../../../../core/shared/operators';
import { ResponsiveTableSizes } from '../../../../shared/responsive-table-sizes/responsive-table-sizes'; import { ResponsiveTableSizes } from '../../../../shared/responsive-table-sizes/responsive-table-sizes';
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
@@ -74,7 +74,7 @@ export class ItemEditBitstreamComponent implements OnChanges, OnInit {
this.bitstream = cloneDeep(this.fieldUpdate.field) as Bitstream; this.bitstream = cloneDeep(this.fieldUpdate.field) as Bitstream;
this.bitstreamName = this.dsoNameService.getName(this.bitstream); this.bitstreamName = this.dsoNameService.getName(this.bitstream);
this.format$ = this.bitstream.format.pipe( this.format$ = this.bitstream.format.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload() getRemoteDataPayload()
); );
} }

View File

@@ -12,11 +12,9 @@ import { SortDirection, SortOptions } from '../../../core/cache/models/sort-opti
import { RestResponse } from '../../../core/cache/response.models'; import { RestResponse } from '../../../core/cache/response.models';
import { CollectionDataService } from '../../../core/data/collection-data.service'; import { CollectionDataService } from '../../../core/data/collection-data.service';
import { ItemDataService } from '../../../core/data/item-data.service'; import { ItemDataService } from '../../../core/data/item-data.service';
import { PaginatedList } from '../../../core/data/paginated-list';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { Collection } from '../../../core/shared/collection.model'; import { Collection } from '../../../core/shared/collection.model';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { PageInfo } from '../../../core/shared/page-info.model';
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
import { SearchService } from '../../../core/shared/search/search.service'; import { SearchService } from '../../../core/shared/search/search.service';
import { ErrorComponent } from '../../../shared/error/error.component'; import { ErrorComponent } from '../../../shared/error/error.component';
@@ -38,6 +36,12 @@ import { SearchServiceStub } from '../../../shared/testing/search-service.stub';
import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe'; import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe';
import { VarDirective } from '../../../shared/utils/var.directive'; import { VarDirective } from '../../../shared/utils/var.directive';
import { ItemCollectionMapperComponent } from './item-collection-mapper.component'; import { ItemCollectionMapperComponent } from './item-collection-mapper.component';
import {
createFailedRemoteDataObject$,
createSuccessfulRemoteDataObject,
createSuccessfulRemoteDataObject$
} from '../../../shared/remote-data.utils';
import { createPaginatedList } from '../../../shared/testing/utils.test';
describe('ItemCollectionMapperComponent', () => { describe('ItemCollectionMapperComponent', () => {
let comp: ItemCollectionMapperComponent; let comp: ItemCollectionMapperComponent;
@@ -55,7 +59,7 @@ describe('ItemCollectionMapperComponent', () => {
id: '932c7d50-d85a-44cb-b9dc-b427b12877bd', id: '932c7d50-d85a-44cb-b9dc-b427b12877bd',
name: 'test-item' name: 'test-item'
}); });
const mockItemRD: RemoteData<Item> = new RemoteData<Item>(false, false, true, null, mockItem); const mockItemRD: RemoteData<Item> = createSuccessfulRemoteDataObject(mockItem);
const mockSearchOptions = of(new PaginatedSearchOptions({ const mockSearchOptions = of(new PaginatedSearchOptions({
pagination: Object.assign(new PaginationComponentOptions(), { pagination: Object.assign(new PaginationComponentOptions(), {
id: 'search-page-configuration', id: 'search-page-configuration',
@@ -74,10 +78,10 @@ describe('ItemCollectionMapperComponent', () => {
const searchConfigServiceStub = { const searchConfigServiceStub = {
paginatedSearchOptions: mockSearchOptions paginatedSearchOptions: mockSearchOptions
}; };
const mockCollectionsRD = new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), [])); const mockCollectionsRD = createSuccessfulRemoteDataObject(createPaginatedList([]));
const itemDataServiceStub = { const itemDataServiceStub = {
mapToCollection: () => of(new RestResponse(true, 200, 'OK')), mapToCollection: () => createSuccessfulRemoteDataObject$({}),
removeMappingFromCollection: () => of(new RestResponse(true, 200, 'OK')), removeMappingFromCollection: () => createSuccessfulRemoteDataObject$({}),
getMappedCollections: () => of(mockCollectionsRD), getMappedCollections: () => of(mockCollectionsRD),
/* tslint:disable:no-empty */ /* tslint:disable:no-empty */
clearMappedCollectionsRequests: () => {} clearMappedCollectionsRequests: () => {}
@@ -143,7 +147,7 @@ describe('ItemCollectionMapperComponent', () => {
}); });
it('should display an error message if at least one mapping was unsuccessful', () => { it('should display an error message if at least one mapping was unsuccessful', () => {
spyOn(itemDataService, 'mapToCollection').and.returnValue(of(new RestResponse(false, 404, 'Not Found'))); spyOn(itemDataService, 'mapToCollection').and.returnValue(createFailedRemoteDataObject$('Not Found', 404));
comp.mapCollections(ids); comp.mapCollections(ids);
expect(notificationsService.success).not.toHaveBeenCalled(); expect(notificationsService.success).not.toHaveBeenCalled();
expect(notificationsService.error).toHaveBeenCalled(); expect(notificationsService.error).toHaveBeenCalled();
@@ -160,7 +164,7 @@ describe('ItemCollectionMapperComponent', () => {
}); });
it('should display an error message if the removal of at least one mapping was unsuccessful', () => { it('should display an error message if the removal of at least one mapping was unsuccessful', () => {
spyOn(itemDataService, 'removeMappingFromCollection').and.returnValue(of(new RestResponse(false, 404, 'Not Found'))); spyOn(itemDataService, 'removeMappingFromCollection').and.returnValue(createFailedRemoteDataObject$('Not Found', 404));
comp.removeMappings(ids); comp.removeMappings(ids);
expect(notificationsService.success).not.toHaveBeenCalled(); expect(notificationsService.success).not.toHaveBeenCalled();
expect(notificationsService.error).toHaveBeenCalled(); expect(notificationsService.error).toHaveBeenCalled();

View File

@@ -4,13 +4,13 @@ import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/
import { CollectionDataService } from '../../../core/data/collection-data.service'; import { CollectionDataService } from '../../../core/data/collection-data.service';
import { fadeIn, fadeInOut } from '../../../shared/animations/fade'; import { fadeIn, fadeInOut } from '../../../shared/animations/fade';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginatedList } from '../../../core/data/paginated-list.model';
import { Collection } from '../../../core/shared/collection.model'; import { Collection } from '../../../core/shared/collection.model';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { import {
getFirstSucceededRemoteDataPayload, getFirstSucceededRemoteDataPayload,
getRemoteDataPayload, getRemoteDataPayload,
getSucceededRemoteData, getFirstSucceededRemoteData,
toDSpaceObjectListRD toDSpaceObjectListRD
} from '../../../core/shared/operators'; } from '../../../core/shared/operators';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
@@ -20,11 +20,11 @@ import { TranslateService } from '@ngx-translate/core';
import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model'; import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
import { isNotEmpty } from '../../../shared/empty.util'; import { isNotEmpty } from '../../../shared/empty.util';
import { RestResponse } from '../../../core/cache/response.models';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model'; import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
import { SearchService } from '../../../core/shared/search/search.service'; import { SearchService } from '../../../core/shared/search/search.service';
import { NoContent } from '../../../core/shared/NoContent.model';
@Component({ @Component({
selector: 'ds-item-collection-mapper', selector: 'ds-item-collection-mapper',
@@ -92,7 +92,7 @@ export class ItemCollectionMapperComponent implements OnInit {
} }
ngOnInit(): void { ngOnInit(): void {
this.itemRD$ = this.route.data.pipe(map((data) => data.dso)).pipe(getSucceededRemoteData()) as Observable<RemoteData<Item>>; this.itemRD$ = this.route.data.pipe(map((data) => data.dso)).pipe(getFirstSucceededRemoteData()) as Observable<RemoteData<Item>>;
this.searchOptions$ = this.searchConfigService.paginatedSearchOptions; this.searchOptions$ = this.searchConfigService.paginatedSearchOptions;
this.loadCollectionLists(); this.loadCollectionLists();
} }
@@ -141,13 +141,13 @@ export class ItemCollectionMapperComponent implements OnInit {
mapCollections(ids: string[]) { mapCollections(ids: string[]) {
const itemIdAndExcludingIds$ = observableCombineLatest( const itemIdAndExcludingIds$ = observableCombineLatest(
this.itemRD$.pipe( this.itemRD$.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
take(1), take(1),
map((rd: RemoteData<Item>) => rd.payload), map((rd: RemoteData<Item>) => rd.payload),
map((item: Item) => item.id) map((item: Item) => item.id)
), ),
this.itemCollectionsRD$.pipe( this.itemCollectionsRD$.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
take(1), take(1),
map((rd: RemoteData<PaginatedList<Collection>>) => rd.payload.page), map((rd: RemoteData<PaginatedList<Collection>>) => rd.payload.page),
map((collections: Collection[]) => collections.map((collection: Collection) => collection.id)) map((collections: Collection[]) => collections.map((collection: Collection) => collection.id))
@@ -168,7 +168,7 @@ export class ItemCollectionMapperComponent implements OnInit {
*/ */
removeMappings(ids: string[]) { removeMappings(ids: string[]) {
const responses$ = this.itemRD$.pipe( const responses$ = this.itemRD$.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
map((itemRD: RemoteData<Item>) => itemRD.payload.id), map((itemRD: RemoteData<Item>) => itemRD.payload.id),
switchMap((itemId: string) => observableCombineLatest(ids.map((id: string) => this.itemDataService.removeMappingFromCollection(itemId, id)))) switchMap((itemId: string) => observableCombineLatest(ids.map((id: string) => this.itemDataService.removeMappingFromCollection(itemId, id))))
); );
@@ -191,10 +191,10 @@ export class ItemCollectionMapperComponent implements OnInit {
* @param {Observable<RestResponse[]>} responses$ The responses after adding/removing a mapping * @param {Observable<RestResponse[]>} responses$ The responses after adding/removing a mapping
* @param {string} messagePrefix The prefix to build the notification messages with * @param {string} messagePrefix The prefix to build the notification messages with
*/ */
private showNotifications(responses$: Observable<RestResponse[]>, messagePrefix: string) { private showNotifications(responses$: Observable<Array<RemoteData<NoContent>>>, messagePrefix: string) {
responses$.subscribe((responses: RestResponse[]) => { responses$.subscribe((responses: Array<RemoteData<NoContent>>) => {
const successful = responses.filter((response: RestResponse) => response.isSuccessful); const successful = responses.filter((response: RemoteData<NoContent>) => response.hasSucceeded);
const unsuccessful = responses.filter((response: RestResponse) => !response.isSuccessful); const unsuccessful = responses.filter((response: RemoteData<NoContent>) => response.hasFailed);
if (successful.length > 0) { if (successful.length > 0) {
const successMessages = observableCombineLatest( const successMessages = observableCombineLatest(
this.translateService.get(`${messagePrefix}.success.head`), this.translateService.get(`${messagePrefix}.success.head`),
@@ -280,7 +280,7 @@ export class ItemCollectionMapperComponent implements OnInit {
*/ */
onCancel() { onCancel() {
this.itemRD$.pipe( this.itemRD$.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
take(1) take(1)
).subscribe((item: Item) => { ).subscribe((item: Item) => {

View File

@@ -16,16 +16,17 @@ import { NotificationsService } from '../../../shared/notifications/notification
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { ItemDeleteComponent } from './item-delete.component'; import { ItemDeleteComponent } from './item-delete.component';
import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils'; import {
createSuccessfulRemoteDataObject,
createSuccessfulRemoteDataObject$
} from '../../../shared/remote-data.utils';
import { VarDirective } from '../../../shared/utils/var.directive'; import { VarDirective } from '../../../shared/utils/var.directive';
import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service'; import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service';
import { RelationshipService } from '../../../core/data/relationship.service'; import { RelationshipService } from '../../../core/data/relationship.service';
import { RelationshipType } from '../../../core/shared/item-relationships/relationship-type.model'; import { RelationshipType } from '../../../core/shared/item-relationships/relationship-type.model';
import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list';
import { PageInfo } from '../../../core/shared/page-info.model';
import { EntityTypeService } from '../../../core/data/entity-type.service'; import { EntityTypeService } from '../../../core/data/entity-type.service';
import { getItemEditRoute } from '../../item-page-routing-paths'; import { getItemEditRoute } from '../../item-page-routing-paths';
import { createPaginatedList } from '../../../shared/testing/utils.test';
let comp: ItemDeleteComponent; let comp: ItemDeleteComponent;
let fixture: ComponentFixture<ItemDeleteComponent>; let fixture: ComponentFixture<ItemDeleteComponent>;
@@ -78,52 +79,16 @@ describe('ItemDeleteComponent', () => {
Object.assign(new Relationship(), { Object.assign(new Relationship(), {
id: '1', id: '1',
uuid: 'relationship-1', uuid: 'relationship-1',
relationshipType: observableOf(new RemoteData( relationshipType: createSuccessfulRemoteDataObject$(type1),
false, leftItem: createSuccessfulRemoteDataObject$(mockItem),
false, rightItem: createSuccessfulRemoteDataObject$(new Item()),
true,
null,
type1
)),
leftItem: observableOf(new RemoteData(
false,
false,
true,
null,
mockItem,
)),
rightItem: observableOf(new RemoteData(
false,
false,
true,
null,
Object.assign(new Item(), {})
)),
}), }),
Object.assign(new Relationship(), { Object.assign(new Relationship(), {
id: '2', id: '2',
uuid: 'relationship-2', uuid: 'relationship-2',
relationshipType: observableOf(new RemoteData( relationshipType: createSuccessfulRemoteDataObject$(type2),
false, leftItem: createSuccessfulRemoteDataObject$(mockItem),
false, rightItem: createSuccessfulRemoteDataObject$(new Item()),
true,
null,
type2
)),
leftItem: observableOf(new RemoteData(
false,
false,
true,
null,
mockItem,
)),
rightItem: observableOf(new RemoteData(
false,
false,
true,
null,
Object.assign(new Item(), {})
)),
}), }),
]; ];
@@ -133,7 +98,7 @@ describe('ItemDeleteComponent', () => {
}); });
mockItemDataService = jasmine.createSpyObj('mockItemDataService', { mockItemDataService = jasmine.createSpyObj('mockItemDataService', {
delete: observableOf(true) delete: createSuccessfulRemoteDataObject$({})
}); });
routeStub = { routeStub = {
@@ -149,20 +114,8 @@ describe('ItemDeleteComponent', () => {
entityTypeService = jasmine.createSpyObj('entityTypeService', entityTypeService = jasmine.createSpyObj('entityTypeService',
{ {
getEntityTypeByLabel: observableOf(new RemoteData( getEntityTypeByLabel: createSuccessfulRemoteDataObject$(itemType),
false, getEntityTypeRelationships: createSuccessfulRemoteDataObject$(createPaginatedList(types)),
false,
true,
null,
itemType,
)),
getEntityTypeRelationships: observableOf(new RemoteData(
false,
false,
true,
null,
new PaginatedList(new PageInfo(), types),
)),
} }
); );
@@ -236,5 +189,4 @@ describe('ItemDeleteComponent', () => {
expect(routerStub.navigate).toHaveBeenCalledWith([getItemEditRoute('fake-id')]); expect(routerStub.navigate).toHaveBeenCalledWith([getItemEditRoute('fake-id')]);
}); });
}); });
}) });
;

View File

@@ -1,12 +1,21 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { defaultIfEmpty, filter, first, map, switchMap, take } from 'rxjs/operators'; import { defaultIfEmpty, filter, map, switchMap, take } from 'rxjs/operators';
import { AbstractSimpleItemActionComponent } from '../simple-item-action/abstract-simple-item-action.component'; import { AbstractSimpleItemActionComponent } from '../simple-item-action/abstract-simple-item-action.component';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { combineLatest as observableCombineLatest, combineLatest, Observable, of as observableOf } from 'rxjs'; import {
combineLatest as observableCombineLatest,
combineLatest,
Observable,
of as observableOf
} from 'rxjs';
import { RelationshipType } from '../../../core/shared/item-relationships/relationship-type.model'; import { RelationshipType } from '../../../core/shared/item-relationships/relationship-type.model';
import { VirtualMetadata } from '../virtual-metadata/virtual-metadata.component'; import { VirtualMetadata } from '../virtual-metadata/virtual-metadata.component';
import { Relationship } from '../../../core/shared/item-relationships/relationship.model'; import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../core/shared/operators'; import {
getRemoteDataPayload,
getFirstSucceededRemoteData,
getFirstCompletedRemoteData
} from '../../../core/shared/operators';
import { hasValue, isNotEmpty } from '../../../shared/empty.util'; import { hasValue, isNotEmpty } from '../../../shared/empty.util';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { MetadataValue } from '../../../core/shared/metadata.models'; import { MetadataValue } from '../../../core/shared/metadata.models';
@@ -20,8 +29,9 @@ import { RelationshipService } from '../../../core/data/relationship.service';
import { EntityTypeService } from '../../../core/data/entity-type.service'; import { EntityTypeService } from '../../../core/data/entity-type.service';
import { LinkService } from '../../../core/cache/builders/link.service'; import { LinkService } from '../../../core/cache/builders/link.service';
import { followLink } from '../../../shared/utils/follow-link-config.model'; import { followLink } from '../../../shared/utils/follow-link-config.model';
import { RestResponse } from '../../../core/cache/response.models';
import { getItemEditRoute } from '../../item-page-routing-paths'; import { getItemEditRoute } from '../../item-page-routing-paths';
import { RemoteData } from '../../../core/data/remote-data';
import { NoContent } from '../../../core/shared/NoContent.model';
@Component({ @Component({
selector: 'ds-item-delete', selector: 'ds-item-delete',
@@ -105,10 +115,10 @@ export class ItemDeleteComponent
const label = this.item.firstMetadataValue('relationship.type'); const label = this.item.firstMetadataValue('relationship.type');
if (label !== undefined) { if (label !== undefined) {
this.types$ = this.entityTypeService.getEntityTypeByLabel(label).pipe( this.types$ = this.entityTypeService.getEntityTypeByLabel(label).pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
switchMap((entityType) => this.entityTypeService.getEntityTypeRelationships(entityType.id)), switchMap((entityType) => this.entityTypeService.getEntityTypeRelationships(entityType.id)),
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
map((relationshipTypes) => relationshipTypes.page), map((relationshipTypes) => relationshipTypes.page),
switchMap((types) => switchMap((types) =>
@@ -220,7 +230,7 @@ export class ItemDeleteComponent
followLink('rightItem'), followLink('rightItem'),
); );
return relationship.relationshipType.pipe( return relationship.relationshipType.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
filter((relationshipType: RelationshipType) => hasValue(relationshipType) && isNotEmpty(relationshipType.uuid)) filter((relationshipType: RelationshipType) => hasValue(relationshipType) && isNotEmpty(relationshipType.uuid))
); );
@@ -238,7 +248,7 @@ export class ItemDeleteComponent
relationship, relationship,
this.isLeftItem(relationship).pipe( this.isLeftItem(relationship).pipe(
switchMap((isLeftItem) => isLeftItem ? relationship.rightItem : relationship.leftItem), switchMap((isLeftItem) => isLeftItem ? relationship.rightItem : relationship.leftItem),
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
), ),
); );
@@ -285,7 +295,7 @@ export class ItemDeleteComponent
private isLeftItem(relationship: Relationship): Observable<boolean> { private isLeftItem(relationship: Relationship): Observable<boolean> {
return relationship.leftItem.pipe( return relationship.leftItem.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
filter((item: Item) => hasValue(item) && isNotEmpty(item.uuid)), filter((item: Item) => hasValue(item) && isNotEmpty(item.uuid)),
map((leftItem) => leftItem.uuid === this.item.uuid) map((leftItem) => leftItem.uuid === this.item.uuid)
@@ -327,9 +337,9 @@ export class ItemDeleteComponent
) )
), ),
).subscribe((types) => { ).subscribe((types) => {
this.itemDataService.delete(this.item.id, types).pipe(first()).subscribe( this.itemDataService.delete(this.item.id, types).pipe(getFirstCompletedRemoteData()).subscribe(
(response: RestResponse) => { (rd: RemoteData<NoContent>) => {
this.notify(response.isSuccessful); this.notify(rd.hasSucceeded);
} }
); );
}); });

View File

@@ -9,7 +9,7 @@ import { TestScheduler } from 'rxjs/testing';
import { MetadataFieldDataService } from '../../../../core/data/metadata-field-data.service'; import { MetadataFieldDataService } from '../../../../core/data/metadata-field-data.service';
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions'; import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
import { PaginatedList } from '../../../../core/data/paginated-list'; import { PaginatedList, buildPaginatedList } from '../../../../core/data/paginated-list.model';
import { MetadataField } from '../../../../core/metadata/metadata-field.model'; import { MetadataField } from '../../../../core/metadata/metadata-field.model';
import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model'; import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model';
import { RegistryService } from '../../../../core/registry/registry.service'; import { RegistryService } from '../../../../core/registry/registry.service';
@@ -66,7 +66,7 @@ describe('EditInPlaceFieldComponent', () => {
beforeEach(async(() => { beforeEach(async(() => {
scheduler = getTestScheduler(); scheduler = getTestScheduler();
paginatedMetadataFields = new PaginatedList(undefined, [mdField1, mdField2, mdField3]); paginatedMetadataFields = buildPaginatedList(undefined, [mdField1, mdField2, mdField3]);
metadataFieldService = jasmine.createSpyObj({ metadataFieldService = jasmine.createSpyObj({
queryMetadataFields: createSuccessfulRemoteDataObject$(paginatedMetadataFields), queryMetadataFields: createSuccessfulRemoteDataObject$(paginatedMetadataFields),
@@ -221,7 +221,7 @@ describe('EditInPlaceFieldComponent', () => {
})); }));
it('it should call queryMetadataFields on the metadataFieldService with the correct query', () => { it('it should call queryMetadataFields on the metadataFieldService with the correct query', () => {
expect(metadataFieldService.queryMetadataFields).toHaveBeenCalledWith(query, null, followLink('schema')); expect(metadataFieldService.queryMetadataFields).toHaveBeenCalledWith(query, null, false, followLink('schema'));
}); });
it('it should set metadataFieldSuggestions to the right value', () => { it('it should set metadataFieldSuggestions to the right value', () => {

View File

@@ -1,10 +1,13 @@
import { Component, Input, OnChanges, OnInit } from '@angular/core'; import { Component, Input, OnChanges, OnInit } from '@angular/core';
import { metadataFieldsToString } from '../../../../core/shared/operators'; import {
metadataFieldsToString,
getFirstSucceededRemoteData
} from '../../../../core/shared/operators';
import { hasValue, isNotEmpty } from '../../../../shared/empty.util'; import { hasValue, isNotEmpty } from '../../../../shared/empty.util';
import { RegistryService } from '../../../../core/registry/registry.service'; import { RegistryService } from '../../../../core/registry/registry.service';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; import { BehaviorSubject, Observable, of as observableOf } from 'rxjs';
import { map, take } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions'; import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
import { FieldUpdate } from '../../../../core/data/object-updates/object-updates.reducer'; import { FieldUpdate } from '../../../../core/data/object-updates/object-updates.reducer';
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
@@ -124,10 +127,10 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges {
*/ */
findMetadataFieldSuggestions(query: string) { findMetadataFieldSuggestions(query: string) {
if (isNotEmpty(query)) { if (isNotEmpty(query)) {
return this.registryService.queryMetadataFields(query, null, followLink('schema')).pipe( return this.registryService.queryMetadataFields(query, null, false, followLink('schema')).pipe(
getFirstSucceededRemoteData(),
metadataFieldsToString(), metadataFieldsToString(),
take(1)) ).subscribe((fieldNames: string[]) => {
.subscribe((fieldNames: string[]) => {
this.setInputSuggestions(fieldNames); this.setInputSuggestions(fieldNames);
}) })
} else { } else {

View File

@@ -21,7 +21,6 @@ import { Item } from '../../../core/shared/item.model';
import { FieldChangeType } from '../../../core/data/object-updates/object-updates.actions'; import { FieldChangeType } from '../../../core/data/object-updates/object-updates.actions';
import { MetadatumViewModel } from '../../../core/shared/metadata.models'; import { MetadatumViewModel } from '../../../core/shared/metadata.models';
import { RegistryService } from '../../../core/registry/registry.service'; import { RegistryService } from '../../../core/registry/registry.service';
import { PaginatedList } from '../../../core/data/paginated-list';
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model'; import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
import { MetadataField } from '../../../core/metadata/metadata-field.model'; import { MetadataField } from '../../../core/metadata/metadata-field.model';
import { import {
@@ -30,6 +29,7 @@ import {
} from '../../../shared/remote-data.utils'; } from '../../../shared/remote-data.utils';
import { ObjectCacheService } from '../../../core/cache/object-cache.service'; import { ObjectCacheService } from '../../../core/cache/object-cache.service';
import { DSOSuccessResponse } from '../../../core/cache/response.models'; import { DSOSuccessResponse } from '../../../core/cache/response.models';
import { createPaginatedList } from '../../../shared/testing/utils.test';
let comp: any; let comp: any;
let fixture: ComponentFixture<ItemMetadataComponent>; let fixture: ComponentFixture<ItemMetadataComponent>;
@@ -133,7 +133,7 @@ describe('ItemMetadataComponent', () => {
data: observableOf({ dso: createSuccessfulRemoteDataObject(item) }) data: observableOf({ dso: createSuccessfulRemoteDataObject(item) })
} }
}; };
paginatedMetadataFields = new PaginatedList(undefined, [mdField1, mdField2, mdField3]); paginatedMetadataFields = createPaginatedList([mdField1, mdField2, mdField3]);
metadataFieldService = jasmine.createSpyObj({ metadataFieldService = jasmine.createSpyObj({
getAllMetadataFields: createSuccessfulRemoteDataObject$(paginatedMetadataFields) getAllMetadataFields: createSuccessfulRemoteDataObject$(paginatedMetadataFields)

View File

@@ -4,19 +4,18 @@ import { ItemDataService } from '../../../core/data/item-data.service';
import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service'; import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import { first, switchMap, tap } from 'rxjs/operators'; import { first, switchMap } from 'rxjs/operators';
import { getSucceededRemoteData } from '../../../core/shared/operators'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { MetadataValue, MetadatumViewModel } from '../../../core/shared/metadata.models'; import { MetadataValue, MetadatumViewModel } from '../../../core/shared/metadata.models';
import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component'; import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component';
import { UpdateDataService } from '../../../core/data/update-data.service'; import { UpdateDataService } from '../../../core/data/update-data.service';
import { hasNoValue, hasValue, isNotEmpty } from '../../../shared/empty.util'; import { hasNoValue, hasValue } from '../../../shared/empty.util';
import { AlertType } from '../../../shared/alert/aletr-type'; import { AlertType } from '../../../shared/alert/aletr-type';
import { Operation } from 'fast-json-patch'; import { Operation } from 'fast-json-patch';
import { METADATA_PATCH_OPERATION_SERVICE_TOKEN } from '../../../core/data/object-updates/patch-operation-service/metadata-patch-operation.service'; import { MetadataPatchOperationService } from '../../../core/data/object-updates/patch-operation-service/metadata-patch-operation.service';
import { DSOSuccessResponse, ErrorResponse } from '../../../core/cache/response.models';
@Component({ @Component({
selector: 'ds-item-metadata', selector: 'ds-item-metadata',
@@ -87,7 +86,7 @@ export class ItemMetadataComponent extends AbstractItemUpdateComponent {
* Sends all initial values of this item to the object updates service * Sends all initial values of this item to the object updates service
*/ */
public initializeOriginalFields() { public initializeOriginalFields() {
this.objectUpdatesService.initialize(this.url, this.item.metadataAsList, this.item.lastModified, METADATA_PATCH_OPERATION_SERVICE_TOKEN); this.objectUpdatesService.initialize(this.url, this.item.metadataAsList, this.item.lastModified, MetadataPatchOperationService);
} }
/** /**
@@ -101,26 +100,20 @@ export class ItemMetadataComponent extends AbstractItemUpdateComponent {
first(), first(),
switchMap((patch: Operation[]) => { switchMap((patch: Operation[]) => {
return this.updateService.patch(this.item, patch).pipe( return this.updateService.patch(this.item, patch).pipe(
tap((response) => { getFirstCompletedRemoteData()
if (!response.isSuccessful) {
this.notificationsService.error(this.getNotificationTitle('error'), (response as ErrorResponse).errorMessage);
}
}),
switchMap((response: DSOSuccessResponse) => {
if (isNotEmpty(response.resourceSelfLinks)) {
return this.itemService.findByHref(response.resourceSelfLinks[0]);
}
}),
getSucceededRemoteData()
); );
}) })
).subscribe( ).subscribe(
(rd: RemoteData<Item>) => { (rd: RemoteData<Item>) => {
this.item = rd.payload; if (rd.hasFailed) {
this.checkAndFixMetadataUUIDs(); this.notificationsService.error(this.getNotificationTitle('error'), rd.errorMessage);
this.initializeOriginalFields(); } else {
this.updates$ = this.objectUpdatesService.getFieldUpdates(this.url, this.item.metadataAsList); this.item = rd.payload;
this.notificationsService.success(this.getNotificationTitle('saved'), this.getNotificationContent('saved')); this.checkAndFixMetadataUUIDs();
this.initializeOriginalFields();
this.updates$ = this.objectUpdatesService.getFieldUpdates(this.url, this.item.metadataAsList);
this.notificationsService.success(this.getNotificationTitle('saved'), this.getNotificationContent('saved'));
}
} }
) )
} else { } else {

View File

@@ -7,10 +7,7 @@ import { RouterTestingModule } from '@angular/router/testing';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { RestResponse } from '../../../core/cache/response.models';
import { ItemDataService } from '../../../core/data/item-data.service'; import { ItemDataService } from '../../../core/data/item-data.service';
import { PaginatedList } from '../../../core/data/paginated-list';
import { RemoteData } from '../../../core/data/remote-data';
import { Collection } from '../../../core/shared/collection.model'; import { Collection } from '../../../core/shared/collection.model';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { SearchService } from '../../../core/shared/search/search.service'; import { SearchService } from '../../../core/shared/search/search.service';
@@ -18,6 +15,12 @@ import { NotificationsService } from '../../../shared/notifications/notification
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
import { RouterStub } from '../../../shared/testing/router.stub'; import { RouterStub } from '../../../shared/testing/router.stub';
import { ItemMoveComponent } from './item-move.component'; import { ItemMoveComponent } from './item-move.component';
import {
createFailedRemoteDataObject$,
createSuccessfulRemoteDataObject,
createSuccessfulRemoteDataObject$
} from '../../../shared/remote-data.utils';
import { createPaginatedList } from '../../../shared/testing/utils.test';
describe('ItemMoveComponent', () => { describe('ItemMoveComponent', () => {
let comp: ItemMoveComponent; let comp: ItemMoveComponent;
@@ -34,22 +37,6 @@ describe('ItemMoveComponent', () => {
url: `${itemPageUrl}/edit` url: `${itemPageUrl}/edit`
}); });
const mockItemDataService = jasmine.createSpyObj({
moveToCollection: observableOf(new RestResponse(true, 200, 'Success'))
});
const mockItemDataServiceFail = jasmine.createSpyObj({
moveToCollection: observableOf(new RestResponse(false, 500, 'Internal server error'))
});
const routeStub = {
data: observableOf({
dso: new RemoteData(false, false, true, null, {
id: 'item1'
})
})
};
const collection1 = Object.assign(new Collection(), { const collection1 = Object.assign(new Collection(), {
uuid: 'collection-uuid-1', uuid: 'collection-uuid-1',
name: 'Test collection 1' name: 'Test collection 1'
@@ -60,18 +47,33 @@ describe('ItemMoveComponent', () => {
name: 'Test collection 2' name: 'Test collection 2'
}); });
const mockItemDataService = jasmine.createSpyObj({
moveToCollection: createSuccessfulRemoteDataObject$(collection1)
});
const mockItemDataServiceFail = jasmine.createSpyObj({
moveToCollection: createFailedRemoteDataObject$('Internal server error', 500)
});
const routeStub = {
data: observableOf({
dso: createSuccessfulRemoteDataObject({
id: 'item1'
})
})
};
const mockSearchService = { const mockSearchService = {
search: () => { search: () => {
return observableOf(new RemoteData(false, false, true, null, return createSuccessfulRemoteDataObject$(createPaginatedList([
new PaginatedList(null, [ {
{ indexableObject: collection1,
indexableObject: collection1, hitHighlights: {}
hitHighlights: {} }, {
}, { indexableObject: collection2,
indexableObject: collection2, hitHighlights: {}
hitHighlights: {} }
} ]));
])));
} }
}; };

View File

@@ -3,15 +3,17 @@ import { first, map } from 'rxjs/operators';
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model'; import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginatedList } from '../../../core/data/paginated-list.model';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { getSucceededRemoteData } from '../../../core/shared/operators'; import {
getFirstSucceededRemoteData,
getFirstCompletedRemoteData
} from '../../../core/shared/operators';
import { ItemDataService } from '../../../core/data/item-data.service'; import { ItemDataService } from '../../../core/data/item-data.service';
import { Observable, of as observableOf } from 'rxjs'; import { Observable, of as observableOf } from 'rxjs';
import { RestResponse } from '../../../core/cache/response.models';
import { Collection } from '../../../core/shared/collection.model'; import { Collection } from '../../../core/shared/collection.model';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { SearchService } from '../../../core/shared/search/search.service'; import { SearchService } from '../../../core/shared/search/search.service';
@@ -55,7 +57,7 @@ export class ItemMoveComponent implements OnInit {
} }
ngOnInit(): void { ngOnInit(): void {
this.itemRD$ = this.route.data.pipe(map((data) => data.dso), getSucceededRemoteData()) as Observable<RemoteData<Item>>; this.itemRD$ = this.route.data.pipe(map((data) => data.dso), getFirstSucceededRemoteData()) as Observable<RemoteData<Item>>;
this.itemRD$.subscribe((rd) => { this.itemRD$.subscribe((rd) => {
this.itemId = rd.payload.id; this.itemId = rd.payload.id;
} }
@@ -114,10 +116,10 @@ export class ItemMoveComponent implements OnInit {
*/ */
moveCollection() { moveCollection() {
this.processing = true; this.processing = true;
this.itemDataService.moveToCollection(this.itemId, this.selectedCollection).pipe(first()).subscribe( this.itemDataService.moveToCollection(this.itemId, this.selectedCollection).pipe(getFirstCompletedRemoteData()).subscribe(
(response: RestResponse) => { (response: RemoteData<Collection>) => {
this.router.navigate([getItemEditRoute(this.itemId)]); this.router.navigate([getItemEditRoute(this.itemId)]);
if (response.isSuccessful) { if (response.hasSucceeded) {
this.notificationsService.success(this.translateService.get('item.edit.move.success')); this.notificationsService.success(this.translateService.get('item.edit.move.success'));
} else { } else {
this.notificationsService.error(this.translateService.get('item.edit.move.error')); this.notificationsService.error(this.translateService.get('item.edit.move.error'));

View File

@@ -2,7 +2,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { RouterStub } from '../../../shared/testing/router.stub'; import { RouterStub } from '../../../shared/testing/router.stub';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data';
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
@@ -16,7 +15,7 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { ItemPrivateComponent } from './item-private.component'; import { ItemPrivateComponent } from './item-private.component';
import { RestResponse } from '../../../core/cache/response.models'; import { RestResponse } from '../../../core/cache/response.models';
import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils'; import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
let comp: ItemPrivateComponent; let comp: ItemPrivateComponent;
let fixture: ComponentFixture<ItemPrivateComponent>; let fixture: ComponentFixture<ItemPrivateComponent>;
@@ -46,14 +45,12 @@ describe('ItemPrivateComponent', () => {
}); });
mockItemDataService = jasmine.createSpyObj('mockItemDataService', { mockItemDataService = jasmine.createSpyObj('mockItemDataService', {
setDiscoverable: observableOf(new RestResponse(true, 200, 'OK')) setDiscoverable: createSuccessfulRemoteDataObject$(mockItem)
}); });
routeStub = { routeStub = {
data: observableOf({ data: observableOf({
dso: createSuccessfulRemoteDataObject({ dso: createSuccessfulRemoteDataObject(mockItem)
id: 'fake-id'
})
}) })
}; };
@@ -98,9 +95,8 @@ describe('ItemPrivateComponent', () => {
spyOn(comp, 'processRestResponse'); spyOn(comp, 'processRestResponse');
comp.performAction(); comp.performAction();
expect(mockItemDataService.setDiscoverable).toHaveBeenCalledWith(mockItem.id, false); expect(mockItemDataService.setDiscoverable).toHaveBeenCalledWith(mockItem, false);
expect(comp.processRestResponse).toHaveBeenCalled(); expect(comp.processRestResponse).toHaveBeenCalled();
}); });
}); });
}) });
;

View File

@@ -1,9 +1,8 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { first } from 'rxjs/operators';
import { AbstractSimpleItemActionComponent } from '../simple-item-action/abstract-simple-item-action.component'; import { AbstractSimpleItemActionComponent } from '../simple-item-action/abstract-simple-item-action.component';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { RestResponse } from '../../../core/cache/response.models'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
@Component({ @Component({
selector: 'ds-item-private', selector: 'ds-item-private',
@@ -21,9 +20,9 @@ export class ItemPrivateComponent extends AbstractSimpleItemActionComponent {
* Perform the make private action to the item * Perform the make private action to the item
*/ */
performAction() { performAction() {
this.itemDataService.setDiscoverable(this.item.id, false).pipe(first()).subscribe( this.itemDataService.setDiscoverable(this.item, false).pipe(getFirstCompletedRemoteData()).subscribe(
(response: RestResponse) => { (rd: RemoteData<Item>) => {
this.processRestResponse(response); this.processRestResponse(rd);
} }
); );
} }

View File

@@ -2,7 +2,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { RouterStub } from '../../../shared/testing/router.stub'; import { RouterStub } from '../../../shared/testing/router.stub';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data';
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
@@ -15,8 +14,7 @@ import { NotificationsService } from '../../../shared/notifications/notification
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { ItemPublicComponent } from './item-public.component'; import { ItemPublicComponent } from './item-public.component';
import { RestResponse } from '../../../core/cache/response.models'; import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils';
let comp: ItemPublicComponent; let comp: ItemPublicComponent;
let fixture: ComponentFixture<ItemPublicComponent>; let fixture: ComponentFixture<ItemPublicComponent>;
@@ -27,8 +25,6 @@ let routerStub;
let mockItemDataService: ItemDataService; let mockItemDataService: ItemDataService;
let routeStub; let routeStub;
let notificationsServiceStub; let notificationsServiceStub;
let successfulRestResponse;
let failRestResponse;
describe('ItemPublicComponent', () => { describe('ItemPublicComponent', () => {
beforeEach(async(() => { beforeEach(async(() => {
@@ -46,14 +42,12 @@ describe('ItemPublicComponent', () => {
}); });
mockItemDataService = jasmine.createSpyObj('mockItemDataService', { mockItemDataService = jasmine.createSpyObj('mockItemDataService', {
setDiscoverable: observableOf(new RestResponse(true, 200, 'OK')) setDiscoverable: createSuccessfulRemoteDataObject$(mockItem)
}); });
routeStub = { routeStub = {
data: observableOf({ data: observableOf({
dso: createSuccessfulRemoteDataObject({ dso: createSuccessfulRemoteDataObject(mockItem)
id: 'fake-id'
})
}) })
}; };
@@ -74,9 +68,6 @@ describe('ItemPublicComponent', () => {
})); }));
beforeEach(() => { beforeEach(() => {
successfulRestResponse = new RestResponse(true, 200, 'OK');
failRestResponse = new RestResponse(false, 500, 'Internal Server Error');
fixture = TestBed.createComponent(ItemPublicComponent); fixture = TestBed.createComponent(ItemPublicComponent);
comp = fixture.componentInstance; comp = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
@@ -98,9 +89,8 @@ describe('ItemPublicComponent', () => {
spyOn(comp, 'processRestResponse'); spyOn(comp, 'processRestResponse');
comp.performAction(); comp.performAction();
expect(mockItemDataService.setDiscoverable).toHaveBeenCalledWith(mockItem.id, true); expect(mockItemDataService.setDiscoverable).toHaveBeenCalledWith(mockItem, true);
expect(comp.processRestResponse).toHaveBeenCalled(); expect(comp.processRestResponse).toHaveBeenCalled();
}); });
}); });
}) });
;

View File

@@ -1,9 +1,8 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { first } from 'rxjs/operators';
import { AbstractSimpleItemActionComponent } from '../simple-item-action/abstract-simple-item-action.component'; import { AbstractSimpleItemActionComponent } from '../simple-item-action/abstract-simple-item-action.component';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { RestResponse } from '../../../core/cache/response.models'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
@Component({ @Component({
selector: 'ds-item-public', selector: 'ds-item-public',
@@ -21,8 +20,8 @@ export class ItemPublicComponent extends AbstractSimpleItemActionComponent {
* Perform the make public action to the item * Perform the make public action to the item
*/ */
performAction() { performAction() {
this.itemDataService.setDiscoverable(this.item.id, true).pipe(first()).subscribe( this.itemDataService.setDiscoverable(this.item, true).pipe(getFirstCompletedRemoteData()).subscribe(
(response: RestResponse) => { (response: RemoteData<Item>) => {
this.processRestResponse(response); this.processRestResponse(response);
} }
); );

View File

@@ -2,7 +2,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { RouterStub } from '../../../shared/testing/router.stub'; import { RouterStub } from '../../../shared/testing/router.stub';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data';
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
@@ -15,8 +14,10 @@ import { NotificationsService } from '../../../shared/notifications/notification
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { ItemReinstateComponent } from './item-reinstate.component'; import { ItemReinstateComponent } from './item-reinstate.component';
import { RestResponse } from '../../../core/cache/response.models'; import {
import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils'; createSuccessfulRemoteDataObject,
createSuccessfulRemoteDataObject$
} from '../../../shared/remote-data.utils';
let comp: ItemReinstateComponent; let comp: ItemReinstateComponent;
let fixture: ComponentFixture<ItemReinstateComponent>; let fixture: ComponentFixture<ItemReinstateComponent>;
@@ -27,8 +28,6 @@ let routerStub;
let mockItemDataService: ItemDataService; let mockItemDataService: ItemDataService;
let routeStub; let routeStub;
let notificationsServiceStub; let notificationsServiceStub;
let successfulRestResponse;
let failRestResponse;
describe('ItemReinstateComponent', () => { describe('ItemReinstateComponent', () => {
beforeEach(async(() => { beforeEach(async(() => {
@@ -46,7 +45,7 @@ describe('ItemReinstateComponent', () => {
}); });
mockItemDataService = jasmine.createSpyObj('mockItemDataService', { mockItemDataService = jasmine.createSpyObj('mockItemDataService', {
setWithDrawn: observableOf(new RestResponse(true, 200, 'OK')) setWithDrawn: createSuccessfulRemoteDataObject$(mockItem)
}); });
routeStub = { routeStub = {
@@ -74,9 +73,6 @@ describe('ItemReinstateComponent', () => {
})); }));
beforeEach(() => { beforeEach(() => {
successfulRestResponse = new RestResponse(true, 200, 'OK');
failRestResponse = new RestResponse(false, 500, 'Internal Server Error');
fixture = TestBed.createComponent(ItemReinstateComponent); fixture = TestBed.createComponent(ItemReinstateComponent);
comp = fixture.componentInstance; comp = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
@@ -98,9 +94,8 @@ describe('ItemReinstateComponent', () => {
spyOn(comp, 'processRestResponse'); spyOn(comp, 'processRestResponse');
comp.performAction(); comp.performAction();
expect(mockItemDataService.setWithDrawn).toHaveBeenCalledWith(mockItem.id, false); expect(mockItemDataService.setWithDrawn).toHaveBeenCalledWith(comp.item, false);
expect(comp.processRestResponse).toHaveBeenCalled(); expect(comp.processRestResponse).toHaveBeenCalled();
}); });
}); });
}) });
;

View File

@@ -1,9 +1,8 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { first } from 'rxjs/operators';
import { AbstractSimpleItemActionComponent } from '../simple-item-action/abstract-simple-item-action.component'; import { AbstractSimpleItemActionComponent } from '../simple-item-action/abstract-simple-item-action.component';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { RestResponse } from '../../../core/cache/response.models'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
@Component({ @Component({
selector: 'ds-item-reinstate', selector: 'ds-item-reinstate',
@@ -21,8 +20,8 @@ export class ItemReinstateComponent extends AbstractSimpleItemActionComponent {
* Perform the reinstate action to the item * Perform the reinstate action to the item
*/ */
performAction() { performAction() {
this.itemDataService.setWithDrawn(this.item.id, false).pipe(first()).subscribe( this.itemDataService.setWithDrawn(this.item, false).pipe(getFirstCompletedRemoteData()).subscribe(
(response: RestResponse) => { (response: RemoteData<Item>) => {
this.processRestResponse(response); this.processRestResponse(response);
} }
); );

View File

@@ -6,19 +6,16 @@ import { of as observableOf } from 'rxjs/internal/observable/of';
import { LinkService } from '../../../../core/cache/builders/link.service'; import { LinkService } from '../../../../core/cache/builders/link.service';
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions'; import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { RelationshipTypeService } from '../../../../core/data/relationship-type.service';
import { RelationshipService } from '../../../../core/data/relationship.service'; import { RelationshipService } from '../../../../core/data/relationship.service';
import { RemoteData } from '../../../../core/data/remote-data';
import { ItemType } from '../../../../core/shared/item-relationships/item-type.model'; import { ItemType } from '../../../../core/shared/item-relationships/item-type.model';
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model'; import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model'; import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
import { Item } from '../../../../core/shared/item.model'; import { Item } from '../../../../core/shared/item.model';
import { PageInfo } from '../../../../core/shared/page-info.model';
import { getMockLinkService } from '../../../../shared/mocks/link-service.mock';
import { SelectableListService } from '../../../../shared/object-list/selectable-list/selectable-list.service'; import { SelectableListService } from '../../../../shared/object-list/selectable-list/selectable-list.service';
import { SharedModule } from '../../../../shared/shared.module'; import { SharedModule } from '../../../../shared/shared.module';
import { EditRelationshipListComponent } from './edit-relationship-list.component'; import { EditRelationshipListComponent } from './edit-relationship-list.component';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
import { createPaginatedList } from '../../../../shared/testing/utils.test';
let comp: EditRelationshipListComponent; let comp: EditRelationshipListComponent;
let fixture: ComponentFixture<EditRelationshipListComponent>; let fixture: ComponentFixture<EditRelationshipListComponent>;
@@ -60,20 +57,8 @@ describe('EditRelationshipListComponent', () => {
relationshipType = Object.assign(new RelationshipType(), { relationshipType = Object.assign(new RelationshipType(), {
id: '1', id: '1',
uuid: '1', uuid: '1',
leftType: observableOf(new RemoteData( leftType: createSuccessfulRemoteDataObject$(entityType),
false, rightType: createSuccessfulRemoteDataObject$(relatedEntityType),
false,
true,
undefined,
entityType,
)),
rightType: observableOf(new RemoteData(
false,
false,
true,
undefined,
relatedEntityType,
)),
leftwardType: 'isAuthorOfPublication', leftwardType: 'isAuthorOfPublication',
rightwardType: 'isPublicationOfAuthor', rightwardType: 'isPublicationOfAuthor',
}); });
@@ -92,53 +77,17 @@ describe('EditRelationshipListComponent', () => {
self: url + '/2', self: url + '/2',
id: '2', id: '2',
uuid: '2', uuid: '2',
relationshipType: observableOf(new RemoteData( relationshipType: createSuccessfulRemoteDataObject$(relationshipType),
false, leftItem: createSuccessfulRemoteDataObject$(item),
false, rightItem: createSuccessfulRemoteDataObject$(author1),
true,
undefined,
relationshipType
)),
leftItem: observableOf(new RemoteData(
false,
false,
true,
undefined,
item,
)),
rightItem: observableOf(new RemoteData(
false,
false,
true,
undefined,
author1,
)),
}), }),
Object.assign(new Relationship(), { Object.assign(new Relationship(), {
self: url + '/3', self: url + '/3',
id: '3', id: '3',
uuid: '3', uuid: '3',
relationshipType: observableOf(new RemoteData( relationshipType: createSuccessfulRemoteDataObject$(relationshipType),
false, leftItem: createSuccessfulRemoteDataObject$(item),
false, rightItem: createSuccessfulRemoteDataObject$(author2),
true,
undefined,
relationshipType
)),
leftItem: observableOf(new RemoteData(
false,
false,
true,
undefined,
item,
)),
rightItem: observableOf(new RemoteData(
false,
false,
true,
undefined,
author2,
)),
}) })
]; ];
@@ -148,13 +97,7 @@ describe('EditRelationshipListComponent', () => {
}, },
id: 'publication', id: 'publication',
uuid: 'publication', uuid: 'publication',
relationships: observableOf(new RemoteData( relationships: createSuccessfulRemoteDataObject$(createPaginatedList(relationships))
false,
false,
true,
undefined,
new PaginatedList(new PageInfo(), relationships),
))
}); });
fieldUpdate1 = { fieldUpdate1 = {
@@ -185,8 +128,8 @@ describe('EditRelationshipListComponent', () => {
relationshipService = jasmine.createSpyObj('relationshipService', relationshipService = jasmine.createSpyObj('relationshipService',
{ {
getRelatedItemsByLabel: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), [author1, author2]))), getRelatedItemsByLabel: createSuccessfulRemoteDataObject$(createPaginatedList([author1, author2])),
getItemRelationshipsByLabel: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), relationships))), getItemRelationshipsByLabel: createSuccessfulRemoteDataObject$(createPaginatedList(relationships)),
isLeftItem: observableOf(true), isLeftItem: observableOf(true),
} }
); );

View File

@@ -10,20 +10,15 @@ import {
RelationshipIdentifiable RelationshipIdentifiable
} from '../../../../core/data/object-updates/object-updates.reducer'; } from '../../../../core/data/object-updates/object-updates.reducer';
import { RelationshipService } from '../../../../core/data/relationship.service'; import { RelationshipService } from '../../../../core/data/relationship.service';
import {Item} from '../../../../core/shared/item.model'; import { Item } from '../../../../core/shared/item.model';
import { import { defaultIfEmpty, flatMap, map, switchMap, take, } from 'rxjs/operators';
defaultIfEmpty, filter, flatMap, import { hasValue, hasValueOperator } from '../../../../shared/empty.util';
map, import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
switchMap, import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
take, tap,
} from 'rxjs/operators';
import { hasValue } from '../../../../shared/empty.util';
import {Relationship} from '../../../../core/shared/item-relationships/relationship.model';
import {RelationshipType} from '../../../../core/shared/item-relationships/relationship-type.model';
import { import {
getAllSucceededRemoteData, getAllSucceededRemoteData,
getRemoteDataPayload, getRemoteDataPayload,
getSucceededRemoteData getFirstSucceededRemoteData,
} from '../../../../core/shared/operators'; } from '../../../../core/shared/operators';
import { combineLatest as observableCombineLatest, of } from 'rxjs'; import { combineLatest as observableCombineLatest, of } from 'rxjs';
import { ItemType } from '../../../../core/shared/item-relationships/item-type.model'; import { ItemType } from '../../../../core/shared/item-relationships/item-type.model';
@@ -33,6 +28,8 @@ import { ItemSearchResult } from '../../../../shared/object-collection/shared/it
import { SelectableListService } from '../../../../shared/object-list/selectable-list/selectable-list.service'; import { SelectableListService } from '../../../../shared/object-list/selectable-list/selectable-list.service';
import { SearchResult } from '../../../../shared/search/search-result.model'; import { SearchResult } from '../../../../shared/search/search-result.model';
import { followLink } from '../../../../shared/utils/follow-link-config.model'; import { followLink } from '../../../../shared/utils/follow-link-config.model';
import { PaginatedList } from '../../../../core/data/paginated-list.model';
import { RemoteData } from '../../../../core/data/remote-data';
@Component({ @Component({
selector: 'ds-edit-relationship-list', selector: 'ds-edit-relationship-list',
@@ -121,10 +118,10 @@ export class EditRelationshipListComponent implements OnInit {
this.relationshipType.leftType, this.relationshipType.leftType,
this.relationshipType.rightType, this.relationshipType.rightType,
].map((itemTypeRD) => itemTypeRD.pipe( ].map((itemTypeRD) => itemTypeRD.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
))).pipe( ))).pipe(
map((itemTypes) => [ map((itemTypes: ItemType[]) => [
this.relationshipType.leftwardType, this.relationshipType.leftwardType,
this.relationshipType.rightwardType, this.relationshipType.rightwardType,
][itemTypes.findIndex((itemType) => itemType.id === this.itemType.id)]), ][itemTypes.findIndex((itemType) => itemType.id === this.itemType.id)]),
@@ -259,9 +256,9 @@ export class EditRelationshipListComponent implements OnInit {
private getRelatedItem(relationship: Relationship): Observable<Item> { private getRelatedItem(relationship: Relationship): Observable<Item> {
return this.relationshipService.isLeftItem(relationship, this.item).pipe( return this.relationshipService.isLeftItem(relationship, this.item).pipe(
switchMap((isLeftItem) => isLeftItem ? relationship.rightItem : relationship.leftItem), switchMap((isLeftItem) => isLeftItem ? relationship.rightItem : relationship.leftItem),
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
) ) as Observable<Item>
} }
ngOnInit(): void { ngOnInit(): void {
@@ -271,10 +268,11 @@ export class EditRelationshipListComponent implements OnInit {
this.relationshipType.leftType, this.relationshipType.leftType,
this.relationshipType.rightType, this.relationshipType.rightType,
].map((type) => type.pipe( ].map((type) => type.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
))).pipe( ))).pipe(
map((relatedTypes) => relatedTypes.find((relatedType) => relatedType.uuid !== this.itemType.uuid)), map((relatedTypes: ItemType[]) => relatedTypes.find((relatedType) => relatedType.uuid !== this.itemType.uuid)),
hasValueOperator()
); );
this.relatedEntityType$.pipe( this.relatedEntityType$.pipe(
@@ -304,9 +302,11 @@ export class EditRelationshipListComponent implements OnInit {
map((fieldUpdates) => { map((fieldUpdates) => {
const fieldUpdatesFiltered: FieldUpdates = {}; const fieldUpdatesFiltered: FieldUpdates = {};
Object.keys(fieldUpdates).forEach((uuid) => { Object.keys(fieldUpdates).forEach((uuid) => {
const field = fieldUpdates[uuid].field; if (hasValue(fieldUpdates[uuid])) {
if ((field as RelationshipIdentifiable).type.id === this.relationshipType.id) { const field = fieldUpdates[uuid].field;
fieldUpdatesFiltered[uuid] = fieldUpdates[uuid]; if ((field as RelationshipIdentifiable).type.id === this.relationshipType.id) {
fieldUpdatesFiltered[uuid] = fieldUpdates[uuid];
}
} }
}); });
return fieldUpdatesFiltered; return fieldUpdatesFiltered;
@@ -316,26 +316,20 @@ export class EditRelationshipListComponent implements OnInit {
} }
private getItemRelationships() { private getItemRelationships() {
this.linkService.resolveLink(this.item, followLink('relationships')); this.linkService.resolveLink(this.item,
followLink('relationships', undefined, true,
followLink('relationshipType'),
followLink('leftItem'),
followLink('rightItem'),
));
return this.item.relationships.pipe( return this.item.relationships.pipe(
getAllSucceededRemoteData(), getAllSucceededRemoteData(),
map((relationships) => relationships.payload.page.filter((relationship) => relationship)), map((relationships: RemoteData<PaginatedList<Relationship>>) => relationships.payload.page.filter((relationship: Relationship) => hasValue(relationship))),
filter((relationships) => relationships.every((relationship) => !!relationship)),
tap((relationships: Relationship[]) =>
relationships.forEach((relationship: Relationship) => {
this.linkService.resolveLinks(
relationship,
followLink('relationshipType'),
followLink('leftItem'),
followLink('rightItem'),
);
})
),
switchMap((itemRelationships: Relationship[]) => switchMap((itemRelationships: Relationship[]) =>
observableCombineLatest( observableCombineLatest(
itemRelationships itemRelationships
.map((relationship) => relationship.relationshipType.pipe( .map((relationship) => relationship.relationshipType.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
)) ))
).pipe( ).pipe(

View File

@@ -4,14 +4,13 @@ import { TranslateModule } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs/internal/observable/of'; import { of as observableOf } from 'rxjs/internal/observable/of';
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions'; import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { RemoteData } from '../../../../core/data/remote-data';
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model'; import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model'; import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
import { Item } from '../../../../core/shared/item.model'; import { Item } from '../../../../core/shared/item.model';
import { PageInfo } from '../../../../core/shared/page-info.model';
import { EditRelationshipComponent } from './edit-relationship.component'; import { EditRelationshipComponent } from './edit-relationship.component';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
import { createPaginatedList } from '../../../../shared/testing/utils.test';
let objectUpdatesService; let objectUpdatesService;
const url = 'http://test-url.com/test-url'; const url = 'http://test-url.com/test-url';
@@ -49,7 +48,7 @@ describe('EditRelationshipComponent', () => {
}, },
id: 'publication', id: 'publication',
uuid: 'publication', uuid: 'publication',
relationships: observableOf(new RemoteData(false, false, true, undefined, new PaginatedList(new PageInfo(), relationships))) relationships: createSuccessfulRemoteDataObject$(createPaginatedList(relationships))
}); });
relatedItem = Object.assign(new Item(), { relatedItem = Object.assign(new Item(), {
@@ -65,9 +64,9 @@ describe('EditRelationshipComponent', () => {
uuid: '2', uuid: '2',
leftId: 'author1', leftId: 'author1',
rightId: 'publication', rightId: 'publication',
relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType)), relationshipType: createSuccessfulRemoteDataObject$(relationshipType),
leftItem: observableOf(new RemoteData(false, false, true, undefined, relatedItem)), leftItem: createSuccessfulRemoteDataObject$(relatedItem),
rightItem: observableOf(new RemoteData(false, false, true, undefined, item)), rightItem: createSuccessfulRemoteDataObject$(item),
}), }),
Object.assign(new Relationship(), { Object.assign(new Relationship(), {
_links: { _links: {
@@ -77,7 +76,7 @@ describe('EditRelationshipComponent', () => {
uuid: '3', uuid: '3',
leftId: 'author2', leftId: 'author2',
rightId: 'publication', rightId: 'publication',
relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType)) relationshipType: createSuccessfulRemoteDataObject$(relationshipType)
}) })
]; ];

View File

@@ -9,7 +9,7 @@ import {
} from '../../../../core/data/object-updates/object-updates.reducer'; } from '../../../../core/data/object-updates/object-updates.reducer';
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
import { Item } from '../../../../core/shared/item.model'; import { Item } from '../../../../core/shared/item.model';
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators'; import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../../../../core/shared/operators';
import { ViewMode } from '../../../../core/shared/view-mode.model'; import { ViewMode } from '../../../../core/shared/view-mode.model';
import { hasValue, isNotEmpty } from '../../../../shared/empty.util'; import { hasValue, isNotEmpty } from '../../../../shared/empty.util';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
@@ -81,12 +81,12 @@ export class EditRelationshipComponent implements OnChanges {
ngOnChanges(): void { ngOnChanges(): void {
if (this.relationship) { if (this.relationship) {
this.leftItem$ = this.relationship.leftItem.pipe( this.leftItem$ = this.relationship.leftItem.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
filter((item: Item) => hasValue(item) && isNotEmpty(item.uuid)) filter((item: Item) => hasValue(item) && isNotEmpty(item.uuid))
); );
this.rightItem$ = this.relationship.rightItem.pipe( this.rightItem$ = this.relationship.rightItem.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
filter((item: Item) => hasValue(item) && isNotEmpty(item.uuid)) filter((item: Item) => hasValue(item) && isNotEmpty(item.uuid))
); );

View File

@@ -11,7 +11,6 @@ import { EntityTypeService } from '../../../core/data/entity-type.service';
import { ItemDataService } from '../../../core/data/item-data.service'; import { ItemDataService } from '../../../core/data/item-data.service';
import { FieldChangeType } from '../../../core/data/object-updates/object-updates.actions'; import { FieldChangeType } from '../../../core/data/object-updates/object-updates.actions';
import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service'; import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service';
import { PaginatedList } from '../../../core/data/paginated-list';
import { RelationshipService } from '../../../core/data/relationship.service'; import { RelationshipService } from '../../../core/data/relationship.service';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { RequestService } from '../../../core/data/request.service'; import { RequestService } from '../../../core/data/request.service';
@@ -29,6 +28,8 @@ import { NotificationsService } from '../../../shared/notifications/notification
import { SharedModule } from '../../../shared/shared.module'; import { SharedModule } from '../../../shared/shared.module';
import { RouterStub } from '../../../shared/testing/router.stub'; import { RouterStub } from '../../../shared/testing/router.stub';
import { ItemRelationshipsComponent } from './item-relationships.component'; import { ItemRelationshipsComponent } from './item-relationships.component';
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
import { createPaginatedList } from '../../../shared/testing/utils.test';
let comp: any; let comp: any;
let fixture: ComponentFixture<ItemRelationshipsComponent>; let fixture: ComponentFixture<ItemRelationshipsComponent>;
@@ -84,7 +85,7 @@ describe('ItemRelationshipsComponent', () => {
}, },
id: '2', id: '2',
uuid: '2', uuid: '2',
relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType)) relationshipType: createSuccessfulRemoteDataObject$(relationshipType)
}), }),
Object.assign(new Relationship(), { Object.assign(new Relationship(), {
_links: { _links: {
@@ -92,7 +93,7 @@ describe('ItemRelationshipsComponent', () => {
}, },
id: '3', id: '3',
uuid: '3', uuid: '3',
relationshipType: observableOf(new RemoteData(false, false, true, undefined, relationshipType)) relationshipType: createSuccessfulRemoteDataObject$(relationshipType)
}) })
]; ];
@@ -102,7 +103,7 @@ describe('ItemRelationshipsComponent', () => {
}, },
id: 'publication', id: 'publication',
uuid: 'publication', uuid: 'publication',
relationships: observableOf(new RemoteData(false, false, true, undefined, new PaginatedList(new PageInfo(), relationships))), relationships: createSuccessfulRemoteDataObject$(createPaginatedList(relationships)),
lastModified: date lastModified: date
}); });
@@ -119,10 +120,10 @@ describe('ItemRelationshipsComponent', () => {
uuid: 'author2' uuid: 'author2'
}); });
relationships[0].leftItem = observableOf(new RemoteData(false, false, true, undefined, author1)); relationships[0].leftItem = createSuccessfulRemoteDataObject$(author1);
relationships[0].rightItem = observableOf(new RemoteData(false, false, true, undefined, item)); relationships[0].rightItem = createSuccessfulRemoteDataObject$(item);
relationships[1].leftItem = observableOf(new RemoteData(false, false, true, undefined, author2)); relationships[1].leftItem = createSuccessfulRemoteDataObject$(author2);
relationships[1].rightItem = observableOf(new RemoteData(false, false, true, undefined, item)); relationships[1].rightItem = createSuccessfulRemoteDataObject$(item);
fieldUpdate1 = { fieldUpdate1 = {
field: relationships[0], field: relationships[0],
@@ -137,12 +138,12 @@ describe('ItemRelationshipsComponent', () => {
}; };
itemService = jasmine.createSpyObj('itemService', { itemService = jasmine.createSpyObj('itemService', {
findById: observableOf(new RemoteData(false, false, true, undefined, item)) findById: createSuccessfulRemoteDataObject$(item)
}); });
routeStub = { routeStub = {
data: observableOf({}), data: observableOf({}),
parent: { parent: {
data: observableOf({ dso: new RemoteData(false, false, true, null, item) }) data: observableOf({ dso: createSuccessfulRemoteDataObject(item) })
} }
}; };
@@ -184,7 +185,7 @@ describe('ItemRelationshipsComponent', () => {
requestService = jasmine.createSpyObj('requestService', requestService = jasmine.createSpyObj('requestService',
{ {
removeByHrefSubstring: {}, removeByHrefSubstring: {},
hasByHrefObservable: observableOf(false) hasByHref$: observableOf(false)
} }
); );
@@ -194,20 +195,8 @@ describe('ItemRelationshipsComponent', () => {
entityTypeService = jasmine.createSpyObj('entityTypeService', entityTypeService = jasmine.createSpyObj('entityTypeService',
{ {
getEntityTypeByLabel: observableOf(new RemoteData( getEntityTypeByLabel: createSuccessfulRemoteDataObject$(entityType),
false, getEntityTypeRelationships: createSuccessfulRemoteDataObject$(createPaginatedList([relationshipType])),
false,
true,
null,
entityType,
)),
getEntityTypeRelationships: observableOf(new RemoteData(
false,
false,
true,
null,
new PaginatedList(new PageInfo(), [relationshipType]),
)),
} }
); );

View File

@@ -1,4 +1,4 @@
import { ChangeDetectorRef, Component, Inject, OnDestroy } from '@angular/core'; import { ChangeDetectorRef, Component } from '@angular/core';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { import {
DeleteRelationship, DeleteRelationship,
@@ -7,8 +7,12 @@ import {
RelationshipIdentifiable, RelationshipIdentifiable,
} from '../../../core/data/object-updates/object-updates.reducer'; } from '../../../core/data/object-updates/object-updates.reducer';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { filter, map, startWith, switchMap, take} from 'rxjs/operators'; import { filter, 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
} from 'rxjs';
import { followLink } from '../../../shared/utils/follow-link-config.model'; import { followLink } from '../../../shared/utils/follow-link-config.model';
import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component'; import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component';
import { ItemDataService } from '../../../core/data/item-data.service'; import { ItemDataService } from '../../../core/data/item-data.service';
@@ -17,16 +21,17 @@ import { ActivatedRoute, Router } from '@angular/router';
import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { RelationshipService } from '../../../core/data/relationship.service'; import { RelationshipService } from '../../../core/data/relationship.service';
import { ErrorResponse, RestResponse } from '../../../core/cache/response.models';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { ObjectCacheService } from '../../../core/cache/object-cache.service'; import { ObjectCacheService } from '../../../core/cache/object-cache.service';
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../core/shared/operators'; import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../../../core/shared/operators';
import { RequestService } from '../../../core/data/request.service'; import { RequestService } from '../../../core/data/request.service';
import { RelationshipType } from '../../../core/shared/item-relationships/relationship-type.model'; import { RelationshipType } from '../../../core/shared/item-relationships/relationship-type.model';
import { ItemType } from '../../../core/shared/item-relationships/item-type.model'; import { ItemType } from '../../../core/shared/item-relationships/item-type.model';
import { EntityTypeService } from '../../../core/data/entity-type.service'; import { EntityTypeService } from '../../../core/data/entity-type.service';
import { FieldChangeType } from '../../../core/data/object-updates/object-updates.actions'; import { FieldChangeType } from '../../../core/data/object-updates/object-updates.actions';
import { Relationship } from '../../../core/shared/item-relationships/relationship.model'; import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
import { NoContent } from '../../../core/shared/NoContent.model';
import { hasValue } from '../../../shared/empty.util';
@Component({ @Component({
selector: 'ds-item-relationships', selector: 'ds-item-relationships',
@@ -78,10 +83,11 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
* Update the item (and view) when it's removed in the request cache * Update the item (and view) when it's removed in the request cache
*/ */
public initializeItemUpdate(): void { public initializeItemUpdate(): void {
this.itemRD$ = this.requestService.hasByHrefObservable(this.item.self).pipe( this.itemRD$ = this.requestService.hasByHref$(this.item.self).pipe(
filter((exists: boolean) => !exists), filter((exists: boolean) => !exists),
switchMap(() => this.itemService.findById( switchMap(() => this.itemService.findById(
this.item.uuid, this.item.uuid,
true,
followLink('owningCollection'), followLink('owningCollection'),
followLink('bundles'), followLink('bundles'),
followLink('relationships')), followLink('relationships')),
@@ -90,7 +96,7 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
); );
this.itemRD$.pipe( this.itemRD$.pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
).subscribe((item) => { ).subscribe((item) => {
this.item = item; this.item = item;
@@ -108,7 +114,7 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
if (label !== undefined) { if (label !== undefined) {
this.entityType$ = this.entityTypeService.getEntityTypeByLabel(label).pipe( this.entityType$ = this.entityTypeService.getEntityTypeByLabel(label).pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
); );
@@ -119,7 +125,7 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
followLink('leftType'), followLink('leftType'),
followLink('rightType')) followLink('rightType'))
.pipe( .pipe(
getSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
map((relationshipTypes) => relationshipTypes.page), map((relationshipTypes) => relationshipTypes.page),
) )
@@ -162,6 +168,7 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
const addRelatedItems$: Observable<RelationshipIdentifiable[]> = this.objectUpdatesService.getFieldUpdates(this.url, []).pipe( const addRelatedItems$: Observable<RelationshipIdentifiable[]> = this.objectUpdatesService.getFieldUpdates(this.url, []).pipe(
map((fieldUpdates: FieldUpdates) => map((fieldUpdates: FieldUpdates) =>
Object.values(fieldUpdates) Object.values(fieldUpdates)
.filter((fieldUpdate: FieldUpdate) => hasValue(fieldUpdate))
.filter((fieldUpdate: FieldUpdate) => fieldUpdate.changeType === FieldChangeType.ADD) .filter((fieldUpdate: FieldUpdate) => fieldUpdate.changeType === FieldChangeType.ADD)
.map((fieldUpdate: FieldUpdate) => fieldUpdate.field as RelationshipIdentifiable) .map((fieldUpdate: FieldUpdate) => fieldUpdate.field as RelationshipIdentifiable)
), ),
@@ -191,7 +198,7 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
}); });
} }
deleteRelationships(deleteRelationshipIDs: DeleteRelationship[]): Observable<RestResponse[]> { deleteRelationships(deleteRelationshipIDs: DeleteRelationship[]): Observable<Array<RemoteData<NoContent>>> {
return observableZip(...deleteRelationshipIDs.map((deleteRelationship) => { return observableZip(...deleteRelationshipIDs.map((deleteRelationship) => {
let copyVirtualMetadata: string; let copyVirtualMetadata: string;
if (deleteRelationship.keepLeftVirtualMetadata && deleteRelationship.keepRightVirtualMetadata) { if (deleteRelationship.keepLeftVirtualMetadata && deleteRelationship.keepRightVirtualMetadata) {
@@ -208,7 +215,7 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
)); ));
} }
addRelationships(addRelatedItems: RelationshipIdentifiable[]): Observable<RestResponse[]> { addRelationships(addRelatedItems: RelationshipIdentifiable[]): Observable<Array<RemoteData<Relationship>>> {
return observableZip(...addRelatedItems.map((addRelationship) => return observableZip(...addRelatedItems.map((addRelationship) =>
this.entityType$.pipe( this.entityType$.pipe(
switchMap((entityType) => this.entityTypeService.isLeftType(addRelationship.type, entityType)), switchMap((entityType) => this.entityTypeService.isLeftType(addRelationship.type, entityType)),
@@ -240,11 +247,11 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
* - Success notification in case there's at least one successful response * - Success notification in case there's at least one successful response
* @param responses * @param responses
*/ */
displayNotifications(responses: RestResponse[]) { displayNotifications(responses: Array<RemoteData<NoContent>>) {
const failedResponses = responses.filter((response: RestResponse) => !response.isSuccessful); const failedResponses = responses.filter((response: RemoteData<NoContent>) => response.hasFailed);
const successfulResponses = responses.filter((response: RestResponse) => response.isSuccessful); const successfulResponses = responses.filter((response: RemoteData<NoContent>) => response.hasSucceeded);
failedResponses.forEach((response: ErrorResponse) => { failedResponses.forEach((response: RemoteData<NoContent>) => {
this.notificationsService.error(this.getNotificationTitle('failed'), response.errorMessage); this.notificationsService.error(this.getNotificationTitle('failed'), response.errorMessage);
}); });
if (successfulResponses.length > 0) { if (successfulResponses.length > 0) {

View File

@@ -3,7 +3,7 @@ import { Observable } from 'rxjs/internal/Observable';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { getSucceededRemoteData } from '../../../core/shared/operators'; import { getFirstSucceededRemoteData } from '../../../core/shared/operators';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { AlertType } from '../../../shared/alert/aletr-type'; import { AlertType } from '../../../shared/alert/aletr-type';
@@ -30,6 +30,6 @@ export class ItemVersionHistoryComponent {
} }
ngOnInit(): void { ngOnInit(): void {
this.itemRD$ = this.route.parent.data.pipe(map((data) => data.dso)).pipe(getSucceededRemoteData()) as Observable<RemoteData<Item>>; this.itemRD$ = this.route.parent.data.pipe(map((data) => data.dso)).pipe(getFirstSucceededRemoteData()) as Observable<RemoteData<Item>>;
} }
} }

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