Merge branch 'main' into w2p-74647_Add-meta-generator-page-header

Conflicts:
	src/app/core/core.module.ts
	src/app/core/data/root.model.ts
	src/app/core/shared/hal-endpoint.service.ts
This commit is contained in:
Kristof De Langhe
2020-12-24 13:44:48 +01:00
587 changed files with 8392 additions and 7155 deletions

View File

@@ -65,6 +65,13 @@ jobs:
- name: Run specs (unit tests)
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
# and load assetstore from a cached copy
- name: Start DSpace REST Backend via Docker (for e2e tests)
@@ -78,10 +85,3 @@ jobs:
- name: Shutdown Docker containers
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",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"sourceMap": {
"scripts": false,
"styles": false,
"hidden": false,
"vendor": false
},
"assets": [
"src/assets"
],

View File

@@ -8,7 +8,7 @@ import { BrowserModule, By } from '@angular/platform-browser';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
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 { FindListOptions } from '../../../core/data/request.models';
import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
@@ -44,7 +44,7 @@ describe('EPeopleRegistryComponent', () => {
activeEPerson: null,
allEpeople: mockEPeople,
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> {
return observableOf(this.activeEPerson);
@@ -54,18 +54,18 @@ describe('EPeopleRegistryComponent', () => {
const result = this.allEpeople.find((ePerson: EPerson) => {
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 (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) => {
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> {
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 { Subscription } from 'rxjs/internal/Subscription';
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 { EPersonDataService } from '../../../core/eperson/eperson-data.service';
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 { FeatureID } from '../../../core/data/feature-authorization/feature-id';
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
import { getAllSucceededRemoteDataPayload } from '../../../core/shared/operators';
import { ErrorResponse, RestResponse } from '../../../core/cache/response.models';
import {
getAllSucceededRemoteDataPayload,
getFirstCompletedRemoteData
} from '../../../core/shared/operators';
import { ConfirmationModalComponent } from '../../../shared/confirmation-modal/confirmation-modal.component';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { RequestService } from '../../../core/data/request.service';
import { filter } from 'rxjs/internal/operators/filter';
import { PageInfo } from '../../../core/shared/page-info.model';
import { NoContent } from '../../../core/shared/NoContent.model';
@Component({
selector: 'ds-epeople-registry',
@@ -159,7 +162,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
})
);
})).pipe(map((dtos: EpersonDtoModel[]) => {
return new PaginatedList(epeople.pageInfo, dtos);
return buildPaginatedList(epeople.pageInfo, dtos);
}))
})).subscribe((value) => {
this.ePeopleDto$.next(value);
@@ -215,13 +218,12 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => {
if (confirm) {
if (hasValue(ePerson.id)) {
this.epersonService.deleteEPerson(ePerson).pipe(take(1)).subscribe((restResponse: RestResponse) => {
if (restResponse.isSuccessful) {
this.epersonService.deleteEPerson(ePerson).pipe(getFirstCompletedRemoteData()).subscribe((restResponse: RemoteData<NoContent>) => {
if (restResponse.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', { name: ePerson.name }));
this.reset();
} else {
const errorResponse = restResponse as ErrorResponse;
this.notificationsService.error('Error occured when trying to delete EPerson with id: ' + ePerson.id + ' with code: ' + errorResponse.statusCode + ' and message: ' + errorResponse.errorMessage);
this.notificationsService.error('Error occured when trying to delete EPerson with id: ' + ePerson.id + ' with code: ' + restResponse.statusCode + ' and message: ' + restResponse.errorMessage);
}
})
}}

View File

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

View File

@@ -10,14 +10,17 @@ import { TranslateService } from '@ngx-translate/core';
import { Subscription, combineLatest, of } from 'rxjs';
import { Observable } from 'rxjs/internal/Observable';
import { switchMap, take } from 'rxjs/operators';
import { RestResponse } from '../../../../core/cache/response.models';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { PaginatedList } from '../../../../core/data/paginated-list.model';
import { RemoteData } from '../../../../core/data/remote-data';
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
import { GroupDataService } from '../../../../core/eperson/group-data.service';
import { EPerson } from '../../../../core/eperson/models/eperson.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 { FormBuilderService } from '../../../../shared/form/builder/form-builder.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 { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { RequestService } from '../../../../core/data/request.service';
import { NoContent } from '../../../../core/shared/NoContent.model';
@Component({
selector: 'ds-eperson-form',
@@ -314,9 +318,11 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
createNewEPerson(values) {
const ePersonToCreate = Object.assign(new EPerson(), values);
const response = this.epersonService.tryToCreate(ePersonToCreate);
response.pipe(take(1)).subscribe((restResponse: RestResponse) => {
if (restResponse.isSuccessful) {
const response = this.epersonService.create(ePersonToCreate);
response.pipe(
getFirstCompletedRemoteData()
).subscribe((rd: RemoteData<EPerson>) => {
if (rd.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.created.success', { name: ePersonToCreate.name }));
this.submitForm.emit(ePersonToCreate);
} else {
@@ -354,8 +360,8 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
});
const response = this.epersonService.updateEPerson(editedEperson);
response.pipe(take(1)).subscribe((restResponse: RestResponse) => {
if (restResponse.isSuccessful) {
response.pipe(take(1)).subscribe((rd: RemoteData<EPerson>) => {
if (rd.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.edited.success', { name: editedEperson.name }));
this.submitForm.emit(editedEperson);
} else {
@@ -380,7 +386,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
this.subs.push(this.epersonService.searchByScope('email', ePerson.email, {
currentPage: 1,
elementsPerPage: 0
}).pipe(getSucceededRemoteData(), getRemoteDataPayload())
}).pipe(getFirstSucceededRemoteData(), getRemoteDataPayload())
.subscribe((list: PaginatedList<EPerson>) => {
if (list.totalElements > 0) {
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) => {
if (confirm) {
if (hasValue(eperson.id)) {
this.epersonService.deleteEPerson(eperson).pipe(take(1)).subscribe((restResponse: RestResponse) => {
if (restResponse.isSuccessful) {
this.epersonService.deleteEPerson(eperson).pipe(take(1)).subscribe((restResponse: RemoteData<NoContent>) => {
if (restResponse.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', { name: eperson.name }));
this.reset();
} 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();
})

View File

@@ -12,11 +12,10 @@ import { of as observableOf } from 'rxjs';
import { Observable } from 'rxjs/internal/Observable';
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.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 { DSpaceObjectDataService } from '../../../../core/data/dspace-object-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 { EPersonDataService } from '../../../../core/eperson/eperson-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 { RouterMock } from '../../../../shared/mocks/router.mock';
import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
import { Operation } from 'fast-json-patch';
describe('GroupFormComponent', () => {
let component: GroupFormComponent;
@@ -71,6 +71,7 @@ describe('GroupFormComponent', () => {
groupsDataServiceStub = {
allGroups: groups,
activeGroup: null,
createdGroup: null,
getActiveGroup(): Observable<Group> {
return observableOf(this.activeGroup);
},
@@ -80,7 +81,10 @@ describe('GroupFormComponent', () => {
editGroup(group: Group) {
this.activeGroup = group
},
updateGroup(group: Group) {
clearGroupsRequests() {
return null;
},
patch(group: Group, operations: Operation[]) {
return null;
},
cancelEditGroup(): void {
@@ -89,12 +93,21 @@ describe('GroupFormComponent', () => {
findById(id: string) {
return observableOf({ payload: null, hasSucceeded: true });
},
tryToCreate(group: Group): Observable<RestResponse> {
this.allGroups = [...this.allGroups, group]
return observableOf(new RestResponse(true, 200, 'Success'));
findByHref(href: string) {
return createSuccessfulRemoteDataObject$(this.createdGroup);
},
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>>> {
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), []))
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []))
},
getGroupEditPageRouterLinkWithID(id: string) {
return `group-edit-page-for-${id}`;
}
};
authorizationService = jasmine.createSpyObj('authorizationService', {
@@ -171,7 +184,7 @@ describe('GroupFormComponent', () => {
describe('with active Group', () => {
beforeEach(() => {
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.onSubmit();
fixture.detectChanges();

View File

@@ -9,15 +9,20 @@ import {
DynamicTextAreaModel
} from '@ng-dynamic-forms/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 { getCollectionEditRolesRoute } from '../../../../+collection-page/collection-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 { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service';
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 { RequestService } from '../../../../core/data/request.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 { Community } from '../../../../core/shared/community.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 { 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 { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { followLink } from '../../../../shared/utils/follow-link-config.model';
import { NoContent } from '../../../../core/shared/NoContent.model';
import { Operation } from 'fast-json-patch';
@Component({
selector: 'ds-group-form',
@@ -135,6 +146,7 @@ export class GroupFormComponent implements OnInit, OnDestroy {
this.setActiveGroup(params.groupId)
}));
this.canEdit$ = this.groupDataService.getActiveGroup().pipe(
hasValueOperator(),
switchMap((group: Group) => {
return observableCombineLatest(
this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(group) ? group.self : undefined),
@@ -231,17 +243,17 @@ export class GroupFormComponent implements OnInit, OnDestroy {
*/
createNewGroup(values) {
const groupToCreate = Object.assign(new Group(), values);
const response = this.groupDataService.tryToCreate(groupToCreate);
response.pipe(take(1)).subscribe((restResponse: RestResponse) => {
if (restResponse.isSuccessful) {
this.groupDataService.create(groupToCreate).pipe(
getFirstCompletedRemoteData()
).subscribe((rd: RemoteData<Group>) => {
if (rd.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.created.success', { name: groupToCreate.name }));
this.submitForm.emit(groupToCreate);
const resp: any = restResponse;
if (isNotEmpty(resp.resourceSelfLinks)) {
const groupSelfLink = resp.resourceSelfLinks[0];
if (isNotEmpty(rd.payload)) {
const groupSelfLink = rd.payload._links.self.href;
this.setActiveGroupWithLink(groupSelfLink);
this.groupDataService.clearGroupsRequests();
this.router.navigateByUrl(this.groupDataService.getGroupEditPageRouterLinkWithID(this.groupDataService.getUUIDFromString(groupSelfLink)));
this.router.navigateByUrl(this.groupDataService.getGroupEditPageRouterLinkWithID(rd.payload.uuid));
}
} else {
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, {
currentPage: 1,
elementsPerPage: 0
}).pipe(getSucceededRemoteData(), getRemoteDataPayload())
}).pipe(getFirstSucceededRemoteData(), getRemoteDataPayload())
.subscribe((list: PaginatedList<Group>) => {
if (list.totalElements > 0) {
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
*/
editGroup(group: Group) {
const editedGroup = Object.assign(new Group(), {
id: group.id,
metadata: {
'dc.description': [
{
value: (hasValue(this.groupDescription.value) ? this.groupDescription.value : group.firstMetadataValue('dc.description'))
let operations: Operation[] = []
if (hasValue(this.groupDescription.value)) {
operations = [...operations, {
op: 'replace',
path: '/metadata/dc.description/0/value',
value: this.groupDescription.value
}];
}
],
},
name: (hasValue(this.groupName.value) ? this.groupName.value : group.name),
_links: group._links,
});
const response = this.groupDataService.updateGroup(editedGroup);
response.pipe(take(1)).subscribe((restResponse: RestResponse) => {
if (restResponse.isSuccessful) {
this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.edited.success', { name: editedGroup.name }));
this.submitForm.emit(editedGroup);
if (hasValue(this.groupName.value)) {
operations = [...operations, {
op: 'replace',
path: '/name',
value: this.groupName.value
}];
}
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 {
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();
}
});
@@ -309,7 +328,7 @@ export class GroupFormComponent implements OnInit, OnDestroy {
this.groupDataService.cancelEditGroup();
this.groupDataService.findById(groupId)
.pipe(
getSucceededRemoteData(),
getFirstSucceededRemoteData(),
getRemoteDataPayload())
.subscribe((group: Group) => {
this.groupDataService.editGroup(group);
@@ -324,9 +343,9 @@ export class GroupFormComponent implements OnInit, OnDestroy {
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => {
if (activeGroup === null) {
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(
getSucceededRemoteData(),
getFirstSucceededRemoteData(),
getRemoteDataPayload())
.subscribe((group: Group) => {
this.groupDataService.editGroup(group);
@@ -350,15 +369,15 @@ export class GroupFormComponent implements OnInit, OnDestroy {
modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => {
if (confirm) {
if (hasValue(group.id)) {
this.groupDataService.deleteGroup(group).pipe(take(1))
.subscribe(([success, optionalErrorMessage]: [boolean, string]) => {
if (success) {
this.groupDataService.delete(group.id).pipe(getFirstCompletedRemoteData())
.subscribe((rd: RemoteData<NoContent>) => {
if (rd.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.deleted.success', { name: group.name }));
this.reset();
} else {
this.notificationsService.error(
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>
</form>
<ds-pagination *ngIf="(ePeopleSearch | async)?.payload.totalElements > 0"
<ds-pagination *ngIf="(ePeopleSearch | async)?.payload?.totalElements > 0"
[paginationOptions]="configSearch"
[pageInfoState]="(ePeopleSearch | async)?.payload"
[collectionSize]="(ePeopleSearch | async)?.payload?.totalElements"
@@ -70,7 +70,7 @@
</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"
role="alert">
{{messagePrefix + '.no-items' | translate}}
@@ -78,7 +78,7 @@
<h4>{{messagePrefix + '.headMembers' | translate}}</h4>
<ds-pagination *ngIf="(ePeopleMembersOfGroup | async)?.payload.totalElements > 0"
<ds-pagination *ngIf="(ePeopleMembersOfGroup | async)?.payload?.totalElements > 0"
[paginationOptions]="config"
[pageInfoState]="(ePeopleMembersOfGroup | async)?.payload"
[collectionSize]="(ePeopleMembersOfGroup | async)?.payload?.totalElements"
@@ -116,7 +116,7 @@
</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">
{{messagePrefix + '.no-members-yet' | translate}}
</div>

View File

@@ -1,6 +1,14 @@
import { CommonModule } from '@angular/common';
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 { BrowserModule, By } from '@angular/platform-browser';
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 { Observable } from 'rxjs/internal/Observable';
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 { EPersonDataService } from '../../../../../core/eperson/eperson-data.service';
import { GroupDataService } from '../../../../../core/eperson/group-data.service';
@@ -52,13 +60,13 @@ describe('MembersListComponent', () => {
epersonMembers: epersonMembers,
subgroupMembers: subgroupMembers,
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>>> {
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() {
// empty
@@ -83,9 +91,9 @@ describe('MembersListComponent', () => {
},
searchGroups(query: string): Observable<RemoteData<PaginatedList<Group>>> {
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> {
this.epersonMembers = [...this.epersonMembers, eperson];

View File

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

View File

@@ -18,7 +18,7 @@
</div>
</form>
<ds-pagination *ngIf="(groupsSearch | async)?.payload.totalElements > 0"
<ds-pagination *ngIf="(groupsSearch | async)?.payload?.totalElements > 0"
[paginationOptions]="configSearch"
[pageInfoState]="(groupsSearch | async)?.payload"
[collectionSize]="(groupsSearch | async)?.payload?.totalElements"
@@ -65,14 +65,14 @@
</div>
</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">
{{messagePrefix + '.no-items' | translate}}
</div>
<h4>{{messagePrefix + '.headSubgroups' | translate}}</h4>
<ds-pagination *ngIf="(subgroupsOfGroup | async)?.payload.totalElements > 0"
<ds-pagination *ngIf="(subgroupsOfGroup | async)?.payload?.totalElements > 0"
[paginationOptions]="config"
[pageInfoState]="(subgroupsOfGroup | async)?.payload"
[collectionSize]="(subgroupsOfGroup | async)?.payload?.totalElements"
@@ -109,7 +109,7 @@
</div>
</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">
{{messagePrefix + '.no-subgroups-yet' | translate}}
</div>

View File

@@ -8,7 +8,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs/internal/Observable';
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 { GroupDataService } from '../../../../../core/eperson/group-data.service';
import { Group } from '../../../../../core/eperson/models/group.model';
@@ -52,16 +52,16 @@ describe('SubgroupsListComponent', () => {
return this.activeGroup;
},
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 {
return '/admin/access-control/groups/' + group.id;
},
searchGroups(query: string): Observable<RemoteData<PaginatedList<Group>>> {
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> {
this.subgroups = [...this.subgroups, subgroup];

View File

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

View File

@@ -2,14 +2,20 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { Router } from '@angular/router';
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 { ObservedValueOf } from 'rxjs/internal/types';
import { catchError, map, switchMap, take } from 'rxjs/operators';
import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service';
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
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 { RequestService } from '../../../core/data/request.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 { RouteService } from '../../../core/services/route.service';
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 { hasValue } from '../../../shared/empty.util';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { NoContent } from '../../../core/shared/NoContent.model';
@Component({
selector: 'ds-groups-registry',
@@ -115,7 +125,8 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
this.subs.push(this.groupService.searchGroups(this.currentSearchQuery.trim(), {
currentPage: this.config.currentPage,
elementsPerPage: this.config.pageSize
}).subscribe((groupsRD: RemoteData<PaginatedList<Group>>) => {
}).pipe(getFirstCompletedRemoteData())
.subscribe((groupsRD: RemoteData<PaginatedList<Group>>) => {
this.groups$.next(groupsRD);
this.pageInfoState$.next(groupsRD.payload.pageInfo);
}
@@ -136,7 +147,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
}
)
})).pipe(map((dtos: GroupDtoModel[]) => {
return new PaginatedList(groups.pageInfo, dtos);
return buildPaginatedList(groups.pageInfo, dtos);
}))
})).subscribe((value: PaginatedList<GroupDtoModel>) => {
this.groupsDto$.next(value);
@@ -149,15 +160,15 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
*/
deleteGroup(group: Group) {
if (hasValue(group.id)) {
this.groupService.deleteGroup(group).pipe(take(1))
.subscribe(([success, optionalErrorMessage]: [boolean, string]) => {
if (success) {
this.groupService.delete(group.id).pipe(getFirstCompletedRemoteData())
.subscribe((rd: RemoteData<NoContent>) => {
if (rd.hasSucceeded) {
this.notificationsService.success(this.translateService.get(this.messagePrefix + 'notification.deleted.success', { name: group.name }));
this.reset();
} else {
this.notificationsService.error(
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 { FileValidator } from '../../shared/utils/require-file.validator';
import { MetadataImportPageComponent } from './metadata-import-page.component';
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
describe('MetadataImportPageComponent', () => {
let comp: MetadataImportPageComponent;
@@ -36,13 +37,7 @@ describe('MetadataImportPageComponent', () => {
notificationService = new NotificationsServiceStub();
scriptService = jasmine.createSpyObj('scriptService',
{
invoke: observableOf({
response:
{
isSuccessful: true,
resourceSelfLinks: ['https://localhost:8080/api/core/processes/45']
}
})
invoke: createSuccessfulRemoteDataObject$({ processId: '45' })
}
);
user = Object.assign(new EPerson(), {
@@ -133,12 +128,7 @@ describe('MetadataImportPageComponent', () => {
describe('if proceed is pressed; but script invoke fails', () => {
beforeEach(fakeAsync(() => {
jasmine.getEnv().allowRespy(true);
spyOn(scriptService, 'invoke').and.returnValue(observableOf({
response:
{
isSuccessful: false,
}
}));
spyOn(scriptService, 'invoke').and.returnValue(createFailedRemoteDataObject$('Error', 500));
const proceed = fixture.debugElement.query(By.css('#proceedButton')).nativeElement;
proceed.click();
fixture.detectChanges();

View File

@@ -3,14 +3,20 @@ import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
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 { METADATA_IMPORT_SCRIPT_NAME, ScriptDataService } from '../../core/data/processes/script-data.service';
import { RequestEntry } from '../../core/data/request.reducer';
import {
METADATA_IMPORT_SCRIPT_NAME,
ScriptDataService
} from '../../core/data/processes/script-data.service';
import { EPerson } from '../../core/eperson/models/eperson.model';
import { ProcessParameter } from '../../process-page/processes/process-parameter.model';
import { isNotEmpty } from '../../shared/empty.util';
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({
selector: 'ds-metadata-import-page',
@@ -79,28 +85,23 @@ export class MetadataImportPageComponent implements OnInit {
Object.assign(new ProcessParameter(), { name: '-f', value: this.fileObject.name }),
];
return this.scriptDataService.invoke(METADATA_IMPORT_SCRIPT_NAME, parameterValues, [this.fileObject])
.pipe(
take(1),
map((requestEntry: RequestEntry) => {
if (requestEntry.response.isSuccessful) {
}
}),
getFirstCompletedRemoteData(),
).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);
const response: any = requestEntry.response;
if (isNotEmpty(response.resourceSelfLinks)) {
const processNumber = response.resourceSelfLinks[0].split('/').pop();
this.router.navigateByUrl('/processes/' + processNumber);
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);
}
}));
}
}),
take(1)
).subscribe();
});
}
}
}

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
import { Component, OnInit } from '@angular/core';
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, zip } from 'rxjs';
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 { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
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 { Router } from '@angular/router';
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
@@ -65,7 +65,7 @@ export class BitstreamFormatsComponent implements OnInit {
const tasks$ = [];
for (const format of formats) {
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[]) => {

View File

@@ -1,11 +1,10 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { find } from 'rxjs/operators';
import { RemoteData } from '../../../core/data/remote-data';
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
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
@@ -25,7 +24,7 @@ export class BitstreamFormatsResolver implements Resolve<RemoteData<BitstreamFor
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<BitstreamFormat>> {
return this.bitstreamFormatDataService.findById(route.params.id)
.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 { TranslateModule } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs';
import { RestResponse } from '../../../../core/cache/response.models';
import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
import { RemoteData } from '../../../../core/data/remote-data';
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 { RouterStub } from '../../../../shared/testing/router.stub';
import { EditBitstreamFormatComponent } from './edit-bitstream-format.component';
import {
createFailedRemoteDataObject$,
createSuccessfulRemoteDataObject,
createSuccessfulRemoteDataObject$
} from '../../../../shared/remote-data.utils';
describe('EditBitstreamFormatComponent', () => {
let comp: EditBitstreamFormatComponent;
@@ -32,7 +36,7 @@ describe('EditBitstreamFormatComponent', () => {
const routeStub = {
data: observableOf({
bitstreamFormat: new RemoteData(false, false, true, null, bitstreamFormat)
bitstreamFormat: createSuccessfulRemoteDataObject(bitstreamFormat)
})
};
@@ -44,7 +48,7 @@ describe('EditBitstreamFormatComponent', () => {
router = new RouterStub();
notificationService = new NotificationsServiceStub();
bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
updateBitstreamFormat: observableOf(new RestResponse(true, 200, 'Success'))
updateBitstreamFormat: createSuccessfulRemoteDataObject$({})
});
TestBed.configureTestingModule({
@@ -73,7 +77,8 @@ describe('EditBitstreamFormatComponent', () => {
it('should initialise the bitstreamFormat based on the route', () => {
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();
notificationService = new NotificationsServiceStub();
bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
updateBitstreamFormat: observableOf(new RestResponse(false, 400, 'Bad Request'))
updateBitstreamFormat: createFailedRemoteDataObject$('Error', 500)
});
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 { Observable } from 'rxjs';
import { Component, OnInit } from '@angular/core';
import { RemoteData } from '../../../../core/data/remote-data';
import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
import { RestResponse } from '../../../../core/cache/response.models';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
import { getBitstreamFormatsModuleRoute } from '../../admin-registries-routing-paths';
import { getFirstCompletedRemoteData } from '../../../../core/shared/operators';
/**
* 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.
*/
updateFormat(bitstreamFormat: BitstreamFormat) {
this.bitstreamFormatDataService.updateBitstreamFormat(bitstreamFormat).pipe(take(1)
).subscribe((response: RestResponse) => {
if (response.isSuccessful) {
this.bitstreamFormatDataService.updateBitstreamFormat(bitstreamFormat).pipe(
getFirstCompletedRemoteData(),
).subscribe((response: RemoteData<BitstreamFormat>) => {
if (response.hasSucceeded) {
this.notificationService.success(this.translateService.get('admin.registries.bitstream-formats.edit.success.head'),
this.translateService.get('admin.registries.bitstream-formats.edit.success.content'));
this.router.navigate([getBitstreamFormatsModuleRoute()]);

View File

@@ -1,7 +1,7 @@
import { MetadataRegistryComponent } from './metadata-registry.component';
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
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 { By } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
@@ -45,7 +45,7 @@ describe('MetadataRegistryComponent', () => {
namespace: 'http://dspace.org/mockschema'
}
];
const mockSchemas = createSuccessfulRemoteDataObject$(new PaginatedList(null, mockSchemasList));
const mockSchemas = createSuccessfulRemoteDataObject$(buildPaginatedList(null, mockSchemasList));
/* tslint:disable:no-empty */
const registryServiceStub = {
getMetadataSchemas: () => mockSchemas,

View File

@@ -2,18 +2,19 @@ import { Component } from '@angular/core';
import { RegistryService } from '../../../core/registry/registry.service';
import { Observable, combineLatest as observableCombineLatest } from 'rxjs';
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 { filter, map, switchMap, take } from 'rxjs/operators';
import { hasValue } from '../../../shared/empty.util';
import { RestResponse } from '../../../core/cache/response.models';
import { zip } from 'rxjs/internal/observable/zip';
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 { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
import { toFindListOptions } from '../../../shared/pagination/pagination.utils';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { NoContent } from '../../../core/shared/NoContent.model';
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
@Component({
selector: 'ds-metadata-registry',
@@ -140,12 +141,12 @@ export class MetadataRegistryComponent {
const tasks$ = [];
for (const schema of schemas) {
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[]) => {
const successResponses = responses.filter((response: RestResponse) => response.isSuccessful);
const failedResponses = responses.filter((response: RestResponse) => !response.isSuccessful);
zip(...tasks$).subscribe((responses: Array<RemoteData<NoContent>>) => {
const successResponses = responses.filter((response: RemoteData<NoContent>) => response.hasSucceeded);
const failedResponses = responses.filter((response: RemoteData<NoContent>) => response.hasFailed);
if (successResponses.length > 0) {
this.showNotification(true, successResponses.length);
}

View File

@@ -1,7 +1,7 @@
import { MetadataSchemaComponent } from './metadata-schema.component';
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
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 { CommonModule } from '@angular/common';
import { ActivatedRoute, Router } from '@angular/router';
@@ -100,11 +100,11 @@ describe('MetadataSchemaComponent', () => {
schema: createSuccessfulRemoteDataObject$(mockSchemasList[1])
}
];
const mockSchemas = createSuccessfulRemoteDataObject$(new PaginatedList(null, mockSchemasList));
const mockSchemas = createSuccessfulRemoteDataObject$(buildPaginatedList(null, mockSchemasList));
/* tslint:disable:no-empty */
const registryServiceStub = {
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]),
getActiveMetadataField: () => observableOf(undefined),
getSelectedMetadataFields: () => observableOf([]),

View File

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

View File

@@ -12,7 +12,7 @@ import { WorkflowItem } from '../../../../../core/submission/models/workflowitem
import { LinkService } from '../../../../../core/cache/builders/link.service';
import { followLink } from '../../../../../shared/utils/follow-link-config.model';
import { Item } from '../../../../../core/shared/item.model';
import { PublicationGridElementComponent } from '../../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component';
import { ItemGridElementComponent } from '../../../../../shared/object-grid/item-grid-element/item-types/item/item-grid-element.component';
import { ListableObjectDirective } from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive';
import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model';
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
@@ -43,7 +43,7 @@ describe('WorkflowItemAdminWorkflowGridElementComponent', () => {
init();
TestBed.configureTestingModule(
{
declarations: [WorkflowItemSearchResultAdminWorkflowGridElementComponent, PublicationGridElementComponent, ListableObjectDirective],
declarations: [WorkflowItemSearchResultAdminWorkflowGridElementComponent, ItemGridElementComponent, ListableObjectDirective],
imports: [
NoopAnimationsModule,
TranslateModule.forRoot(),
@@ -60,7 +60,7 @@ describe('WorkflowItemAdminWorkflowGridElementComponent', () => {
})
.overrideComponent(WorkflowItemSearchResultAdminWorkflowGridElementComponent, {
set: {
entryComponents: [PublicationGridElementComponent]
entryComponents: [ItemGridElementComponent]
}
})
.compileComponents();

View File

@@ -2,11 +2,10 @@ import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { RemoteData } from '../core/data/remote-data';
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 { BitstreamDataService } from '../core/data/bitstream-data.service';
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
@@ -24,9 +23,9 @@ export class BitstreamPageResolver implements Resolve<RemoteData<Bitstream>> {
* or an error if something went wrong
*/
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(
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 { TranslateModule } from '@ngx-translate/core';
import { RouterTestingModule } from '@angular/router/testing';
import { RemoteData } from '../../core/data/remote-data';
import { of as observableOf } from 'rxjs/internal/observable/of';
import {ActivatedRoute, Router} from '@angular/router';
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 { hasValue } from '../../shared/empty.util';
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 { RestResponse } from '../../core/cache/response.models';
import { VarDirective } from '../../shared/utils/var.directive';
import {
createSuccessfulRemoteDataObject,
createSuccessfulRemoteDataObject$
} from '../../shared/remote-data.utils';
import {RouterStub} from '../../shared/testing/router.stub';
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 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: {
self: 'bitstream-selflink'
},
@@ -120,14 +118,14 @@ describe('EditBitstreamPageComponent', () => {
})
});
bitstreamService = jasmine.createSpyObj('bitstreamService', {
findById: observableOf(new RemoteData(false, false, true, null, bitstream)),
update: observableOf(new RemoteData(false, false, true, null, bitstream)),
updateFormat: observableOf(new RestResponse(true, 200, 'OK')),
findById: createSuccessfulRemoteDataObject$(bitstream),
update: createSuccessfulRemoteDataObject$(bitstream),
updateFormat: createSuccessfulRemoteDataObject$(bitstream),
commitUpdates: {},
patch: {}
});
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`;
@@ -140,7 +138,7 @@ describe('EditBitstreamPageComponent', () => {
providers: [
{ provide: NotificationsService, useValue: notificationsService },
{ 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: BitstreamFormatDataService, useValue: bitstreamFormatService },
{ provide: Router, useValue: routerStub },

View File

@@ -22,19 +22,19 @@ import {
getAllSucceededRemoteDataPayload,
getFirstSucceededRemoteDataPayload,
getRemoteDataPayload,
getSucceededRemoteData
getFirstSucceededRemoteData,
getFirstCompletedRemoteData
} from '../../core/shared/operators';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { BitstreamFormatDataService } from '../../core/data/bitstream-format-data.service';
import { BitstreamFormat } from '../../core/shared/bitstream-format.model';
import { BitstreamFormatSupportLevel } from '../../core/shared/bitstream-format-support-level';
import { RestResponse } from '../../core/cache/response.models';
import { hasValue, isNotEmpty } from '../../shared/empty.util';
import { Metadata } from '../../core/shared/metadata.utils';
import { Location } from '@angular/common';
import { Observable } from 'rxjs/internal/Observable';
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 { Bundle } from '../../core/shared/bundle.model';
import { Item } from '../../core/shared/item.model';
@@ -299,12 +299,12 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
this.bitstreamFormatsRD$ = this.bitstreamFormatService.findAll(this.findAllOptions);
const bitstream$ = this.bitstreamRD$.pipe(
getSucceededRemoteData(),
getFirstSucceededRemoteData(),
getRemoteDataPayload()
);
const allFormats$ = this.bitstreamFormatsRD$.pipe(
getSucceededRemoteData(),
getFirstSucceededRemoteData(),
getRemoteDataPayload()
);
@@ -438,16 +438,15 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
if (isNewFormat) {
bitstream$ = this.bitstreamService.updateFormat(this.bitstream, selectedFormat).pipe(
switchMap((formatResponse: RestResponse) => {
if (hasValue(formatResponse) && !formatResponse.isSuccessful) {
getFirstCompletedRemoteData(),
map((formatResponse: RemoteData<Bitstream>) => {
if (hasValue(formatResponse) && formatResponse.hasFailed) {
this.notificationsService.error(
this.translate.instant(this.NOTIFICATIONS_PREFIX + 'error.format.title'),
formatResponse.statusText
formatResponse.errorMessage
);
} else {
return this.bitstreamService.findById(this.bitstream.id).pipe(
getFirstSucceededRemoteDataPayload()
);
return formatResponse.payload;
}
})
);

View File

@@ -1,4 +1,4 @@
import { ChangeDetectorRef, Component, Inject } from '@angular/core';
import { ChangeDetectorRef, Component } from '@angular/core';
import {
BrowseByMetadataPageComponent,
browseParamsToOptions
@@ -43,7 +43,7 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
ngOnInit(): void {
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(
observableCombineLatest(
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 { Observable } from 'rxjs/internal/Observable';
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 { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-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>>> {
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 { 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 { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
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 { Item } from '../../core/shared/item.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 { DSpaceObject } from '../../core/shared/dspace-object.model';
import { take } from 'rxjs/operators';
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
import { BrowseByType, rendersBrowseBy } from '../+browse-by-switcher/browse-by-decorator';
@@ -105,7 +104,7 @@ export class BrowseByMetadataPageComponent implements OnInit {
}
ngOnInit(): void {
this.updatePage(new BrowseEntrySearchOptions(null, this.paginationConfig, this.sortConfig));
this.updatePage(new BrowseEntrySearchOptions(this.defaultBrowseId, this.paginationConfig, this.sortConfig));
this.subs.push(
observableCombineLatest(
this.route.params,
@@ -169,7 +168,7 @@ export class BrowseByMetadataPageComponent implements OnInit {
updateParent(scope: string) {
if (hasValue(scope)) {
this.parent$ = this.dsoService.findById(scope).pipe(
getSucceededRemoteData()
getFirstSucceededRemoteData()
);
}
}
@@ -179,11 +178,11 @@ export class BrowseByMetadataPageComponent implements OnInit {
*/
goPrev() {
if (this.items$) {
this.items$.pipe(take(1)).subscribe((items) => {
this.items$.pipe(getFirstSucceededRemoteData()).subscribe((items) => {
this.items$ = this.browseService.getPrevBrowseItems(items);
});
} else if (this.browseEntries$) {
this.browseEntries$.pipe(take(1)).subscribe((entries) => {
this.browseEntries$.pipe(getFirstSucceededRemoteData()).subscribe((entries) => {
this.browseEntries$ = this.browseService.getPrevBrowseEntries(entries);
});
}
@@ -194,11 +193,11 @@ export class BrowseByMetadataPageComponent implements OnInit {
*/
goNext() {
if (this.items$) {
this.items$.pipe(take(1)).subscribe((items) => {
this.items$.pipe(getFirstSucceededRemoteData()).subscribe((items) => {
this.items$ = this.browseService.getNextBrowseItems(items);
});
} else if (this.browseEntries$) {
this.browseEntries$.pipe(take(1)).subscribe((entries) => {
this.browseEntries$.pipe(getFirstSucceededRemoteData()).subscribe((entries) => {
this.browseEntries$ = this.browseService.getNextBrowseEntries(entries);
});
}

View File

@@ -32,7 +32,7 @@ export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent {
ngOnInit(): void {
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(
observableCombineLatest(
this.route.params,

View File

@@ -6,7 +6,7 @@ import { Collection } from '../core/shared/collection.model';
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { BreadcrumbConfig } from '../breadcrumbs/breadcrumb/breadcrumb-config.model';
import { Observable } from 'rxjs';
import { getRemoteDataPayload, getSucceededRemoteData } from '../core/shared/operators';
import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../core/shared/operators';
import { map } from 'rxjs/operators';
import { hasValue } from '../shared/empty.util';
import { getDSORoute } from '../app-routing-paths';
@@ -29,7 +29,7 @@ export class BrowseByDSOBreadcrumbResolver {
const uuid = route.queryParams.scope;
if (hasValue(uuid)) {
return this.dataService.findById(uuid).pipe(
getSucceededRemoteData(),
getFirstSucceededRemoteData(),
getRemoteDataPayload(),
map((object: Community | Collection) => {
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 { hasNoValue, hasValue } from '../shared/empty.util';
import { map } from 'rxjs/operators';
import { getSucceededRemoteData } from '../core/shared/operators';
import { getFirstSucceededRemoteData } from '../core/shared/operators';
import { TranslateService } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs';
import { environment } from '../../environments/environment';
@@ -32,7 +32,7 @@ export class BrowseByGuard implements CanActivate {
const value = route.queryParams.value;
const metadataTranslated = this.translate.instant('browse.metadata.' + id);
if (hasValue(scope)) {
const dsoAndMetadata$ = this.dsoService.findById(scope).pipe(getSucceededRemoteData());
const dsoAndMetadata$ = this.dsoService.findById(scope).pipe(getFirstSucceededRemoteData());
return dsoAndMetadata$.pipe(
map((dsoRD) => {
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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
@@ -22,8 +21,6 @@ import { EventEmitter } from '@angular/core';
import { HostWindowService } from '../../shared/host-window.service';
import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub';
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 { PaginationComponent } from '../../shared/pagination/pagination.component';
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 { SearchService } from '../../core/shared/search/search.service';
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', () => {
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({
pagination: Object.assign(new PaginationComponentOptions(), {
id: 'search-page-configuration',
@@ -81,7 +84,7 @@ describe('CollectionItemMapperComponent', () => {
paginatedSearchOptions: mockSearchOptions
};
const itemDataServiceStub = {
mapToCollection: () => of(new RestResponse(true, 200, 'OK'))
mapToCollection: () => createSuccessfulRemoteDataObject$({})
};
const activatedRouteStub = new ActivatedRouteStub({}, { dso: mockCollectionRD });
const translateServiceStub = {
@@ -90,7 +93,7 @@ describe('CollectionItemMapperComponent', () => {
onTranslationChange: 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(), {
search: () => of(emptyList),
/* tslint:disable:no-empty */
@@ -167,7 +170,7 @@ describe('CollectionItemMapperComponent', () => {
});
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);
expect(notificationsService.success).not.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 { RemoteData } from '../../core/data/remote-data';
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 { getRemoteDataPayload, getSucceededRemoteData, toDSpaceObjectListRD } from '../../core/shared/operators';
import {
getRemoteDataPayload,
getFirstSucceededRemoteData,
toDSpaceObjectListRD
} from '../../core/shared/operators';
import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { DSpaceObjectType } from '../../core/shared/dspace-object-type.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 { CollectionDataService } from '../../core/data/collection-data.service';
import { isNotEmpty } from '../../shared/empty.util';
import { RestResponse } from '../../core/cache/response.models';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { SEARCH_CONFIG_SERVICE } from '../../+my-dspace-page/my-dspace-page.component';
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
import { SearchService } from '../../core/shared/search/search.service';
import { followLink } from '../../shared/utils/follow-link-config.model';
import { NoContent } from '../../core/shared/NoContent.model';
@Component({
selector: 'ds-collection-item-mapper',
@@ -102,7 +106,7 @@ export class CollectionItemMapperComponent implements OnInit {
}
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.loadItemLists();
}
@@ -151,7 +155,7 @@ export class CollectionItemMapperComponent implements OnInit {
*/
mapItems(ids: string[], remove?: boolean) {
const responses$ = this.collectionRD$.pipe(
getSucceededRemoteData(),
getFirstSucceededRemoteData(),
map((collectionRD: RemoteData<Collection>) => collectionRD.payload),
switchMap((collection: Collection) =>
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 {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';
responses$.subscribe((responses: RestResponse[]) => {
const successful = responses.filter((response: RestResponse) => response.isSuccessful);
const unsuccessful = responses.filter((response: RestResponse) => !response.isSuccessful);
responses$.subscribe((responses: Array<RemoteData<NoContent>>) => {
const successful = responses.filter((response: RemoteData<any>) => response.hasSucceeded);
const unsuccessful = responses.filter((response: RemoteData<any>) => response.hasFailed);
if (successful.length > 0) {
const successMessages = observableCombineLatest(
this.translateService.get(`collection.edit.item-mapper.notifications.${messageInsertion}.success.head`),
@@ -246,7 +250,7 @@ export class CollectionItemMapperComponent implements OnInit {
*/
onCancel() {
this.collectionRD$.pipe(
getSucceededRemoteData(),
getFirstSucceededRemoteData(),
getRemoteDataPayload(),
take(1)
).subscribe((collection: Collection) => {

View File

@@ -1,12 +1,12 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { BehaviorSubject, of as observableOf, Observable, Subject } from 'rxjs';
import { filter, flatMap, map, startWith, switchMap, take, tap } from 'rxjs/operators';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { filter, flatMap, map, startWith, switchMap, take } from 'rxjs/operators';
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
import { SearchService } from '../core/shared/search/search.service';
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
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 { 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 { Item } from '../core/shared/item.model';
import {
getSucceededRemoteData,
getFirstSucceededRemoteData,
redirectOn4xx,
toDSpaceObjectListRD
} from '../core/shared/operators';
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 { AuthService } from '../core/auth/auth.service';
@@ -81,7 +81,7 @@ export class CollectionPageComponent implements OnInit {
this.itemRD$ = this.paginationChanges$.pipe(
switchMap((dto) => this.collectionRD$.pipe(
getSucceededRemoteData(),
getFirstSucceededRemoteData(),
map((rd) => rd.payload.id),
switchMap((id: string) => {
return this.searchService.search(

View File

@@ -1,6 +1,6 @@
import { first } from 'rxjs/operators';
import { of as observableOf } from 'rxjs';
import { CollectionPageResolver } from './collection-page.resolver';
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
describe('CollectionPageResolver', () => {
describe('resolve', () => {
@@ -10,17 +10,18 @@ describe('CollectionPageResolver', () => {
beforeEach(() => {
collectionService = {
findById: (id: string) => observableOf({ payload: { id }, hasSucceeded: true })
findById: (id: string) => createSuccessfulRemoteDataObject$({ id })
};
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)
.pipe(first())
.subscribe(
(resolved) => {
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 { CollectionDataService } from '../core/data/collection-data.service';
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 { getFirstCompletedRemoteData } from '../core/shared/operators';
/**
* 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
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Collection>> {
return this.collectionService.findById(route.params.id, followLink('logo')).pipe(
find((RD) => hasValue(RD.error) || RD.hasSucceeded),
return this.collectionService.findById(route.params.id, false, followLink('logo')).pipe(
getFirstCompletedRemoteData()
);
}
}

View File

@@ -18,7 +18,7 @@ describe('CreateCollectionPageGuard', () => {
} else if (id === 'invalid-id') {
return createSuccessfulRemoteDataObject$(undefined);
} 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 { RemoteData } from '../../core/data/remote-data';
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 { getFirstCompletedRemoteData } from '../../core/shared/operators';
/**
* Prevent creation of a collection without a parent community provided
@@ -30,7 +31,7 @@ export class CreateCollectionPageGuard implements CanActivate {
}
return this.communityService.findById(parentID)
.pipe(
find((communityRD: RemoteData<Community>) => hasValue(communityRD.payload) || hasValue(communityRD.error)),
getFirstCompletedRemoteData(),
map((communityRD: RemoteData<Community>) => hasValue(communityRD) && communityRD.hasSucceeded && hasValue(communityRD.payload)),
tap((isValid: boolean) => {
if (!isValid) {

View File

@@ -0,0 +1,3 @@
<div class="container">
<ds-resource-policies [resourceType]="'collection'" [resourceUUID]="(dsoRD$ | async)?.payload?.id"></ds-resource-policies>
</div>

View File

@@ -0,0 +1,73 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectorRef, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute } from '@angular/router';
import { cold } from 'jasmine-marbles';
import { of as observableOf } from 'rxjs';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { CollectionAuthorizationsComponent } from './collection-authorizations.component';
import { Collection } from '../../../core/shared/collection.model';
import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils';
describe('CollectionAuthorizationsComponent', () => {
let comp: CollectionAuthorizationsComponent<DSpaceObject>;
let fixture: ComponentFixture<CollectionAuthorizationsComponent<any>>;
const collection = Object.assign(new Collection(), {
uuid: 'collection',
id: 'collection',
_links: {
self: { href: 'collection-selflink' }
}
});
const collectionRD = createSuccessfulRemoteDataObject(collection);
const routeStub = {
parent: {
parent: {
data: observableOf({
dso: collectionRD
})
}
}
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CommonModule
],
declarations: [CollectionAuthorizationsComponent],
providers: [
{ provide: ActivatedRoute, useValue: routeStub },
ChangeDetectorRef,
CollectionAuthorizationsComponent,
],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CollectionAuthorizationsComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
});
afterEach(() => {
comp = null;
fixture.destroy();
});
it('should create', () => {
expect(comp).toBeTruthy();
});
it('should init dso remote data properly', (done) => {
const expected = cold('(a|)', { a: collectionRD });
expect(comp.dsoRD$).toBeObservable(expected);
done();
});
});

View File

@@ -0,0 +1,40 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { first, map } from 'rxjs/operators';
import { RemoteData } from '../../../core/data/remote-data';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
@Component({
selector: 'ds-collection-authorizations',
templateUrl: './collection-authorizations.component.html',
})
/**
* Component that handles the Collection Authorizations
*/
export class CollectionAuthorizationsComponent<TDomain extends DSpaceObject> implements OnInit {
/**
* The initial DSO object
*/
public dsoRD$: Observable<RemoteData<TDomain>>;
/**
* Initialize instance variables
*
* @param {ActivatedRoute} route
*/
constructor(
private route: ActivatedRoute
) {
}
/**
* Initialize the component, setting up the collection
*/
ngOnInit(): void {
this.dsoRD$ = this.route.parent.parent.data.pipe(first(), map((data) => data.dso));
}
}

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ import { Observable } from 'rxjs';
import { first, map } from 'rxjs/operators';
import { RemoteData } from '../../../core/data/remote-data';
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';
/**
@@ -18,21 +18,33 @@ export class CollectionRolesComponent implements OnInit {
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.
*/
getComcolRoles(): Observable<HALLink[]> {
return this.collection$.pipe(
comcolRoles$: Observable<HALLink[]>
/**
* 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) => [
{
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 { By } from '@angular/platform-browser';
import { Collection } from '../../../core/shared/collection.model';
import { RemoteData } from '../../../core/data/remote-data';
import { CollectionDataService } from '../../../core/data/collection-data.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 warningNotification: INotification = new Notification('id', NotificationType.Warning, 'warning');
@@ -111,7 +111,7 @@ describe('CollectionSourceComponent', () => {
uuid: 'fake-collection-id'
});
collectionService = jasmine.createSpyObj('collectionService', {
getContentSource: observableOf(contentSource),
getContentSource: createSuccessfulRemoteDataObject$(contentSource),
updateContentSource: observableOf(contentSource),
getHarvesterEndpoint: observableOf('harvester-endpoint')
});
@@ -125,7 +125,7 @@ describe('CollectionSourceComponent', () => {
{ provide: NotificationsService, useValue: notificationsService },
{ provide: Location, useValue: location },
{ 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: CollectionDataService, useValue: collectionService },
{ 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 { cloneDeep } from 'lodash';
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 { INotification } from '../../../shared/notifications/models/notification.model';
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$.pipe(
getSucceededRemoteData(),
getFirstSucceededRemoteData(),
map((col) => col.payload.uuid),
switchMap((uuid) => this.collectionService.getContentSource(uuid)),
take(1)
).subscribe((contentSource: ContentSource) => {
this.initializeOriginalContentSource(contentSource);
getFirstCompletedRemoteData(),
).subscribe((rd: RemoteData<ContentSource>) => {
this.initializeOriginalContentSource(rd.payload);
});
this.updateFieldTranslations();
@@ -376,7 +376,7 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
onSubmit() {
// Remove cached harvester request to allow for latest harvester to be displayed when switching tabs
this.collectionRD$.pipe(
getSucceededRemoteData(),
getFirstSucceededRemoteData(),
map((col) => col.payload.uuid),
switchMap((uuid) => this.collectionService.getHarvesterEndpoint(uuid)),
take(1)
@@ -384,7 +384,7 @@ export class CollectionSourceComponent extends AbstractTrackableComponent implem
// Update harvester
this.collectionRD$.pipe(
getSucceededRemoteData(),
getFirstSucceededRemoteData(),
map((col) => col.payload.uuid),
switchMap((uuid) => this.collectionService.updateContentSource(uuid, this.contentSource)),
take(1)

View File

@@ -8,6 +8,7 @@ import { CollectionPageModule } from '../collection-page.module';
import { CollectionRolesComponent } from './collection-roles/collection-roles.component';
import { CollectionCurateComponent } from './collection-curate/collection-curate.component';
import { CollectionSourceComponent } from './collection-source/collection-source.component';
import { CollectionAuthorizationsComponent } from './collection-authorizations/collection-authorizations.component';
/**
* Module that contains all components related to the Edit Collection page administrator functionality
@@ -24,7 +25,8 @@ import { CollectionSourceComponent } from './collection-source/collection-source
CollectionMetadataComponent,
CollectionRolesComponent,
CollectionCurateComponent,
CollectionSourceComponent
CollectionSourceComponent,
CollectionAuthorizationsComponent
]
})
export class EditCollectionPageModule {

View File

@@ -5,7 +5,12 @@ import { CollectionMetadataComponent } from './collection-metadata/collection-me
import { CollectionRolesComponent } from './collection-roles/collection-roles.component';
import { CollectionSourceComponent } from './collection-source/collection-source.component';
import { CollectionCurateComponent } from './collection-curate/collection-curate.component';
import { CollectionAuthorizationsComponent } from './collection-authorizations/collection-authorizations.component';
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
import { ResourcePolicyTargetResolver } from '../../shared/resource-policies/resolvers/resource-policy-target.resolver';
import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component';
import { ResourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver';
import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component';
/**
* Routing module that handles the routing for the Edit Collection page administrator functionality
@@ -49,10 +54,46 @@ import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.r
path: 'curate',
component: CollectionCurateComponent,
data: { title: 'collection.edit.tabs.curate.title', showBreadcrumbs: true }
},
/* {
path: 'authorizations',
component: CollectionAuthorizationsComponent,
data: { title: 'collection.edit.tabs.authorizations.title', showBreadcrumbs: true }
},*/
{
path: 'authorizations',
data: { showBreadcrumbs: true },
children: [
{
path: 'create',
resolve: {
resourcePolicyTarget: ResourcePolicyTargetResolver
},
component: ResourcePolicyCreateComponent,
data: { title: 'resource-policies.create.page.title' }
},
{
path: 'edit',
resolve: {
resourcePolicy: ResourcePolicyResolver
},
component: ResourcePolicyEditComponent,
data: { title: 'resource-policies.edit.page.title' }
},
{
path: '',
component: CollectionAuthorizationsComponent,
data: { title: 'collection.edit.tabs.authorizations.title', showBreadcrumbs: true }
}
]
}
]
}
])
],
providers: [
ResourcePolicyResolver,
ResourcePolicyTargetResolver
]
})
export class EditCollectionPageRoutingModule {

View File

@@ -1,6 +1,6 @@
import { of as observableOf } from 'rxjs/internal/observable/of';
import { first } from 'rxjs/operators';
import { ItemTemplatePageResolver } from './item-template-page.resolver';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
describe('ItemTemplatePageResolver', () => {
describe('resolve', () => {
@@ -10,17 +10,18 @@ describe('ItemTemplatePageResolver', () => {
beforeEach(() => {
itemTemplateService = {
findByCollectionID: (id: string) => observableOf({ payload: { id }, hasSucceeded: true })
findByCollectionID: (id: string) => createSuccessfulRemoteDataObject$({ id })
};
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)
.pipe(first())
.subscribe(
(resolved) => {
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 { ItemTemplateDataService } from '../../core/data/item-template-data.service';
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 { getFirstCompletedRemoteData } from '../../core/shared/operators';
/**
* 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
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> {
return this.itemTemplateService.findByCollectionID(route.params.id, followLink('templateItemOf')).pipe(
find((RD) => hasValue(RD.error) || RD.hasSucceeded),
return this.itemTemplateService.findByCollectionID(route.params.id, false, followLink('templateItemOf')).pipe(
getFirstCompletedRemoteData(),
);
}
}

View File

@@ -1,8 +1,8 @@
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 { Subscription, Observable } from 'rxjs';
import { Observable } from 'rxjs';
import { CommunityDataService } from '../core/data/community-data.service';
import { RemoteData } from '../core/data/remote-data';
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 { CommunityPageResolver } from './community-page.resolver';
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
describe('CommunityPageResolver', () => {
describe('resolve', () => {
@@ -10,17 +10,18 @@ describe('CommunityPageResolver', () => {
beforeEach(() => {
communityService = {
findById: (id: string) => observableOf({ payload: { id }, hasSucceeded: true })
findById: (id: string) => createSuccessfulRemoteDataObject$({ id })
};
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)
.pipe(first())
.subscribe(
(resolved) => {
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 { Community } from '../core/shared/community.model';
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 { getFirstCompletedRemoteData } from '../core/shared/operators';
/**
* 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>> {
return this.communityService.findById(
route.params.id,
false,
followLink('logo'),
followLink('subcommunities'),
followLink('collections')
).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 { Community } from '../../core/shared/community.model';
import { first } from 'rxjs/operators';
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
import {
createFailedRemoteDataObject$,
createSuccessfulRemoteDataObject$
} from '../../shared/remote-data.utils';
describe('CreateCommunityPageGuard', () => {
describe('canActivate', () => {
@@ -18,7 +21,7 @@ describe('CreateCommunityPageGuard', () => {
} else if (id === 'invalid-id') {
return createSuccessfulRemoteDataObject$(undefined);
} 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 { RemoteData } from '../../core/data/remote-data';
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 { getFirstCompletedRemoteData } from '../../core/shared/operators';
/**
* 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)
.pipe(
find((communityRD: RemoteData<Community>) => hasValue(communityRD.payload) || hasValue(communityRD.error)),
getFirstCompletedRemoteData(),
map((communityRD: RemoteData<Community>) => hasValue(communityRD) && communityRD.hasSucceeded && hasValue(communityRD.payload)),
tap((isValid: boolean) => {
if (!isValid) {

View File

@@ -0,0 +1,3 @@
<div class="container">
<ds-resource-policies [resourceType]="'community'" [resourceUUID]="(dsoRD$ | async)?.payload?.id"></ds-resource-policies>
</div>

View File

@@ -0,0 +1,73 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectorRef, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute } from '@angular/router';
import { cold } from 'jasmine-marbles';
import { of as observableOf } from 'rxjs';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils';
import { CommunityAuthorizationsComponent } from './community-authorizations.component';
import { Collection } from '../../../core/shared/collection.model';
describe('CommunityAuthorizationsComponent', () => {
let comp: CommunityAuthorizationsComponent<DSpaceObject>;
let fixture: ComponentFixture<CommunityAuthorizationsComponent<any>>;
const community = Object.assign(new Collection(), {
uuid: 'community',
id: 'community',
_links: {
self: { href: 'community-selflink' }
}
});
const communityRD = createSuccessfulRemoteDataObject(community);
const routeStub = {
parent: {
parent: {
data: observableOf({
dso: communityRD
})
}
}
};
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CommonModule
],
declarations: [CommunityAuthorizationsComponent],
providers: [
{ provide: ActivatedRoute, useValue: routeStub },
ChangeDetectorRef,
CommunityAuthorizationsComponent,
],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CommunityAuthorizationsComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
});
afterEach(() => {
comp = null;
fixture.destroy();
});
it('should create', () => {
expect(comp).toBeTruthy();
});
it('should init dso remote data properly', (done) => {
const expected = cold('(a|)', { a: communityRD });
expect(comp.dsoRD$).toBeObservable(expected);
done();
});
});

View File

@@ -0,0 +1,38 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { first, map } from 'rxjs/operators';
import { RemoteData } from 'src/app/core/data/remote-data';
import { DSpaceObject } from 'src/app/core/shared/dspace-object.model';
@Component({
selector: 'ds-community-authorizations',
templateUrl: './community-authorizations.component.html',
})
/**
* Component that handles the community Authorizations
*/
export class CommunityAuthorizationsComponent<TDomain extends DSpaceObject> implements OnInit {
/**
* The initial DSO object
*/
public dsoRD$: Observable<RemoteData<TDomain>>;
/**
* Initialize instance variables
*
* @param {ActivatedRoute} route
*/
constructor(
private route: ActivatedRoute
) {
}
/**
* Initialize the component, setting up the community
*/
ngOnInit(): void {
this.dsoRD$ = this.route.parent.parent.data.pipe(first(), map((data) => data.dso));
}
}

View File

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

View File

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

View File

@@ -7,6 +7,7 @@ import { EditCommunityPageComponent } from './edit-community-page.component';
import { CommunityCurateComponent } from './community-curate/community-curate.component';
import { CommunityMetadataComponent } from './community-metadata/community-metadata.component';
import { CommunityRolesComponent } from './community-roles/community-roles.component';
import { CommunityAuthorizationsComponent } from './community-authorizations/community-authorizations.component';
/**
* Module that contains all components related to the Edit Community page administrator functionality
@@ -22,7 +23,8 @@ import { CommunityRolesComponent } from './community-roles/community-roles.compo
EditCommunityPageComponent,
CommunityCurateComponent,
CommunityMetadataComponent,
CommunityRolesComponent
CommunityRolesComponent,
CommunityAuthorizationsComponent
]
})
export class EditCommunityPageModule {

View File

@@ -1,4 +1,3 @@
import { CommunityPageResolver } from '../community-page.resolver';
import { EditCommunityPageComponent } from './edit-community-page.component';
import { RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
@@ -6,6 +5,11 @@ import { CommunityMetadataComponent } from './community-metadata/community-metad
import { CommunityRolesComponent } from './community-roles/community-roles.component';
import { CommunityCurateComponent } from './community-curate/community-curate.component';
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
import { CommunityAuthorizationsComponent } from './community-authorizations/community-authorizations.component';
import { ResourcePolicyTargetResolver } from '../../shared/resource-policies/resolvers/resource-policy-target.resolver';
import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component';
import { ResourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver';
import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component';
/**
* Routing module that handles the routing for the Edit Community page administrator functionality
@@ -44,11 +48,47 @@ import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.r
path: 'curate',
component: CommunityCurateComponent,
data: { title: 'community.edit.tabs.curate.title', showBreadcrumbs: true }
},
/*{
path: 'authorizations',
component: CommunityAuthorizationsComponent,
data: { title: 'community.edit.tabs.authorizations.title', showBreadcrumbs: true }
},*/
{
path: 'authorizations',
data: { showBreadcrumbs: true },
children: [
{
path: 'create',
resolve: {
resourcePolicyTarget: ResourcePolicyTargetResolver
},
component: ResourcePolicyCreateComponent,
data: { title: 'resource-policies.create.page.title' }
},
{
path: 'edit',
resolve: {
resourcePolicy: ResourcePolicyResolver
},
component: ResourcePolicyEditComponent,
data: { title: 'resource-policies.edit.page.title' }
},
{
path: '',
component: CommunityAuthorizationsComponent,
data: { title: 'community.edit.tabs.authorizations.title', showBreadcrumbs: true, hideReturnButton: true }
}
]
}
]
}
])
],
providers: [
ResourcePolicyResolver,
ResourcePolicyTargetResolver
]
})
export class EditCommunityPageRoutingModule {

View File

@@ -13,7 +13,7 @@ import { SharedModule } from '../../shared/shared.module';
import { CollectionDataService } from '../../core/data/collection-data.service';
import { FindListOptions } from '../../core/data/request.models';
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 { HostWindowService } from '../../shared/host-window.service';
import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub';
@@ -105,7 +105,7 @@ describe('CommunityPageSubCollectionList Component', () => {
if (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 { BehaviorSubject } from 'rxjs';
import { take } from 'rxjs/operators';
import { RemoteData } from '../../core/data/remote-data';
import { Collection } from '../../core/shared/collection.model';
import { Community } from '../../core/shared/community.model';
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 { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { CollectionDataService } from '../../core/data/collection-data.service';
import { takeUntilCompletedRemoteData } from '../../core/shared/operators';
@Component({
selector: 'ds-community-page-sub-collection-list',
@@ -72,7 +72,7 @@ export class CommunityPageSubCollectionListComponent implements OnInit {
currentPage: this.config.currentPage,
elementsPerPage: this.config.pageSize,
sort: { field: this.sortConfig.field, direction: this.sortConfig.direction }
}).pipe(take(1)).subscribe((results) => {
}).pipe(takeUntilCompletedRemoteData()).subscribe((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 { 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 { SharedModule } from '../../shared/shared.module';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
@@ -106,7 +106,7 @@ describe('CommunityPageSubCommunityListComponent Component', () => {
if (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 { BehaviorSubject } from 'rxjs';
import { take } from 'rxjs/operators';
import { RemoteData } from '../../core/data/remote-data';
import { Community } from '../../core/shared/community.model';
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 { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { CommunityDataService } from '../../core/data/community-data.service';
import { takeUntilCompletedRemoteData } from '../../core/shared/operators';
@Component({
selector: 'ds-community-page-sub-community-list',
@@ -75,7 +75,7 @@ export class CommunityPageSubCommunityListComponent implements OnInit {
currentPage: this.config.currentPage,
elementsPerPage: this.config.pageSize,
sort: { field: this.sortConfig.field, direction: this.sortConfig.direction }
}).pipe(take(1)).subscribe((results) => {
}).pipe(takeUntilCompletedRemoteData()).subscribe((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 { 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 { SharedModule } from '../../shared/shared.module';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
@@ -96,7 +96,7 @@ describe('TopLevelCommunityList Component', () => {
if (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 { take } from 'rxjs/operators';
import { BehaviorSubject, Subscription } from 'rxjs';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
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 { Community } from '../../core/shared/community.model';
import { fadeInOut } from '../../shared/animations/fade';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { hasValue } from '../../shared/empty.util';
/**
* this component renders the Top-Level Community list
@@ -22,7 +22,7 @@ import { PaginationComponentOptions } from '../../shared/pagination/pagination-c
animations: [fadeInOut]
})
export class TopLevelCommunityListComponent implements OnInit {
export class TopLevelCommunityListComponent implements OnInit, OnDestroy {
/**
* A list of remote data objects of all top communities
*/
@@ -43,6 +43,11 @@ export class TopLevelCommunityListComponent implements OnInit {
*/
sortConfig: SortOptions;
/**
* The subscription to the observable for the current page.
*/
currentPageSubscription: Subscription;
constructor(private cds: CommunityDataService) {
this.config = new PaginationComponentOptions();
this.config.id = this.pageId;
@@ -71,12 +76,29 @@ export class TopLevelCommunityListComponent implements OnInit {
* Update the list of top communities
*/
updatePage() {
this.cds.findTop({
this.unsubscribe();
this.currentPageSubscription = this.cds.findTop({
currentPage: this.config.currentPage,
elementsPerPage: this.config.pageSize,
sort: { field: this.sortConfig.field, direction: this.sortConfig.direction }
}).pipe(take(1)).subscribe((results) => {
}).subscribe((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 { Bundle } from '../../../core/shared/bundle.model';
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 { RouterStub } from '../../../shared/testing/router.stub';
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
@@ -192,7 +195,7 @@ describe('UploadBistreamComponent', () => {
function createUploadBitstreamTestingModule(queryParams) {
routeStub = {
data: observableOf({
item: createSuccessfulRemoteDataObject(mockItem)
dso: createSuccessfulRemoteDataObject(mockItem)
}),
queryParams: observableOf(queryParams),
snapshot: {

View File

@@ -11,7 +11,7 @@ import { ItemDataService } from '../../../core/data/item-data.service';
import { AuthService } from '../../../core/auth/auth.service';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
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 { BundleDataService } from '../../../core/data/bundle-data.service';
import {
@@ -103,7 +103,7 @@ export class UploadBitstreamComponent implements OnInit, OnDestroy {
*/
ngOnInit(): void {
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(
switchMap((itemRD: RemoteData<Item>) => itemRD.payload.bundles)
);

View File

@@ -6,6 +6,7 @@ import {
createFailedRemoteDataObject,
createSuccessfulRemoteDataObject
} from '../../shared/remote-data.utils';
import { isNotEmpty } from '../../shared/empty.util';
describe('findSuccessfulAccordingTo', () => {
let mockItem1;
@@ -19,12 +20,12 @@ describe('findSuccessfulAccordingTo', () => {
mockItem2 = new Item();
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', () => {
const testRD = {
a: createSuccessfulRemoteDataObject(undefined),
b: createFailedRemoteDataObject(mockItem1),
b: createFailedRemoteDataObject(),
c: createSuccessfulRemoteDataObject(mockItem2),
d: createSuccessfulRemoteDataObject(mockItem1),
e: createSuccessfulRemoteDataObject(mockItem2),

View File

@@ -30,8 +30,6 @@ import { PaginatedDragAndDropBitstreamListComponent } from './item-bitstreams/it
import { VirtualMetadataComponent } from './virtual-metadata/virtual-metadata.component';
import { ItemVersionHistoryComponent } from './item-version-history/item-version-history.component';
import { ItemAuthorizationsComponent } from './item-authorizations/item-authorizations.component';
import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component';
import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component';
import { ObjectValuesPipe } from '../../shared/utils/object-values-pipe';
/**
@@ -71,9 +69,7 @@ import { ObjectValuesPipe } from '../../shared/utils/object-values-pipe';
ItemMoveComponent,
ItemEditBitstreamDragHandleComponent,
VirtualMetadataComponent,
ItemAuthorizationsComponent,
ResourcePolicyEditComponent,
ResourcePolicyCreateComponent,
ItemAuthorizationsComponent
],
providers: [
BundleDataService,

View File

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

View File

@@ -4,7 +4,7 @@ import { ActivatedRoute } from '@angular/router';
import { BehaviorSubject, Observable, of as observableOf, Subscription } from 'rxjs';
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 {
getFirstSucceededRemoteDataPayload,
getFirstSucceededRemoteDataWithNotEmptyPayload
@@ -89,7 +89,7 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
getFirstSucceededRemoteDataWithNotEmptyPayload(),
catchError((error) => {
console.error(error);
return observableOf(new PaginatedList(null, []))
return observableOf(buildPaginatedList(null, []))
})
);
@@ -139,7 +139,7 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
getFirstSucceededRemoteDataPayload(),
catchError((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 { 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 { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ItemBitstreamsComponent } from './item-bitstreams.component';
@@ -13,7 +10,10 @@ import { ActivatedRoute, Router } from '@angular/router';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { ChangeDetectorRef, NO_ERRORS_SCHEMA } from '@angular/core';
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 { BitstreamDataService } from '../../../core/data/bitstream-data.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 { RouterStub } from '../../../shared/testing/router.stub';
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 fixture: ComponentFixture<ItemBitstreamsComponent>;
@@ -55,7 +60,7 @@ const bundle = Object.assign(new Bundle(), {
_links: {
self: { href: 'bundle1-selflink' }
},
bitstreams: createMockRDPaginatedObs([bitstream1, bitstream2])
bitstreams: createSuccessfulRemoteDataObject$(createPaginatedList([bitstream1, bitstream2]))
});
const moveOperations = [
{
@@ -130,17 +135,17 @@ describe('ItemBitstreamsComponent', () => {
_links: {
self: { href: 'item-selflink' }
},
bundles: createMockRDPaginatedObs([bundle]),
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([bundle])),
lastModified: date
});
itemService = Object.assign( {
getBitstreams: () => createMockRDPaginatedObs([bitstream1, bitstream2]),
findById: () => createMockRDObs(item),
getBundles: () => createMockRDPaginatedObs([bundle])
getBitstreams: () => createSuccessfulRemoteDataObject$(createPaginatedList([bitstream1, bitstream2])),
findById: () => createSuccessfulRemoteDataObject$(item),
getBundles: () => createSuccessfulRemoteDataObject$(createPaginatedList([bundle]))
});
route = Object.assign({
parent: {
data: observableOf({ dso: createMockRD(item) })
data: observableOf({ dso: createSuccessfulRemoteDataObject(item) })
},
data: observableOf({}),
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 { hasValue, isNotEmpty } from '../../../shared/empty.util';
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 { 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 { 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 { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer';
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 { ResponsiveColumnSizes } from '../../../shared/responsive-table-sizes/responsive-column-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({
selector: 'ds-item-bitstreams',
@@ -107,7 +108,7 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
*/
postItemInit(): void {
this.bundles$ = this.itemService.getBundles(this.item.id, new PaginatedSearchOptions({pagination: this.bundlesOptions})).pipe(
getSucceededRemoteData(),
getFirstSucceededRemoteData(),
getRemoteDataPayload(),
map((bundlePage: PaginatedList<Bundle>) => bundlePage.page)
);
@@ -125,10 +126,10 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
* Also re-initialize the original fields and updates
*/
initializeItemUpdate(): void {
this.itemUpdateSubscription = this.requestService.hasByHrefObservable(this.item.self).pipe(
this.itemUpdateSubscription = this.requestService.hasByHref$(this.item.self).pipe(
filter((exists: boolean) => !exists),
switchMap(() => this.itemService.findById(this.item.uuid)),
getSucceededRemoteData(),
getFirstSucceededRemoteData(),
).subscribe((itemRD: RemoteData<Item>) => {
if (hasValue(itemRD)) {
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
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.reset();
this.submitting = false;
@@ -190,12 +191,12 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
dropBitstream(bundle: Bundle, event: any) {
this.zone.runOutsideAngular(() => {
if (hasValue(event) && hasValue(event.fromIndex) && hasValue(event.toIndex) && hasValue(event.finish)) {
const moveOperation = Object.assign({
const moveOperation = {
op: 'move',
from: `/_links/bitstreams/${event.fromIndex}/href`,
path: `/_links/bitstreams/${event.toIndex}/href`
});
this.bundleService.patch(bundle, [moveOperation]).pipe(take(1)).subscribe((response: RestResponse) => {
} as Operation;
this.bundleService.patch(bundle, [moveOperation]).pipe(take(1)).subscribe((response: RemoteData<Bundle>) => {
this.zone.run(() => {
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
@@ -216,12 +217,12 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
* @param key The i18n key for the notification messages
* @param responses The returned responses to display notifications for
*/
displayNotifications(key: string, responses: RestResponse[]) {
displayNotifications(key: string, responses: Array<RemoteData<any>>) {
if (isNotEmpty(responses)) {
const failedResponses = responses.filter((response: RestResponse) => hasValue(response) && !response.isSuccessful);
const successfulResponses = responses.filter((response: RestResponse) => hasValue(response) && response.isSuccessful);
const failedResponses = responses.filter((response: RemoteData<Bundle>) => hasValue(response) && response.hasFailed);
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);
});
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 { ObjectUpdatesService } from '../../../../../core/data/object-updates/object-updates.service';
import { BundleDataService } from '../../../../../core/data/bundle-data.service';
import { createMockRDObs } from '../../item-bitstreams.component.spec';
import { Bitstream } from '../../../../../core/shared/bitstream.model';
import { BitstreamFormat } from '../../../../../core/shared/bitstream-format.model';
import { of as observableOf } from 'rxjs/internal/observable/of';
@@ -49,7 +48,7 @@ describe('PaginatedDragAndDropBitstreamListComponent', () => {
name: 'Fake Bitstream 1',
bundleName: 'ORIGINAL',
description: 'Description',
format: createMockRDObs(format)
format: createSuccessfulRemoteDataObject$(format)
});
const fieldUpdate1 = {
field: bitstream1,
@@ -60,7 +59,7 @@ describe('PaginatedDragAndDropBitstreamListComponent', () => {
name: 'Fake Bitstream 2',
bundleName: 'ORIGINAL',
description: 'Description',
format: createMockRDObs(format)
format: createSuccessfulRemoteDataObject$(format)
});
const fieldUpdate2 = {
field: bitstream2,
@@ -107,7 +106,7 @@ describe('PaginatedDragAndDropBitstreamListComponent', () => {
objectValuesPipe = new ObjectValuesPipe();
requestService = jasmine.createSpyObj('requestService', {
hasByHrefObservable: observableOf(true)
hasByHref$: observableOf(true)
});
TestBed.configureTestingModule({

View File

@@ -53,7 +53,7 @@ export class PaginatedDragAndDropBitstreamListComponent extends AbstractPaginate
switchMap((page: number) => {
const paginatedOptions = new PaginatedSearchOptions({pagination: Object.assign({}, this.options, { currentPage: page })});
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(
this.bundle.id,
paginatedOptions,

View File

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

View File

@@ -12,11 +12,9 @@ import { SortDirection, SortOptions } from '../../../core/cache/models/sort-opti
import { RestResponse } from '../../../core/cache/response.models';
import { CollectionDataService } from '../../../core/data/collection-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 { Item } from '../../../core/shared/item.model';
import { PageInfo } from '../../../core/shared/page-info.model';
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
import { SearchService } from '../../../core/shared/search/search.service';
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 { VarDirective } from '../../../shared/utils/var.directive';
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', () => {
let comp: ItemCollectionMapperComponent;
@@ -55,7 +59,7 @@ describe('ItemCollectionMapperComponent', () => {
id: '932c7d50-d85a-44cb-b9dc-b427b12877bd',
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({
pagination: Object.assign(new PaginationComponentOptions(), {
id: 'search-page-configuration',
@@ -74,10 +78,10 @@ describe('ItemCollectionMapperComponent', () => {
const searchConfigServiceStub = {
paginatedSearchOptions: mockSearchOptions
};
const mockCollectionsRD = new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []));
const mockCollectionsRD = createSuccessfulRemoteDataObject(createPaginatedList([]));
const itemDataServiceStub = {
mapToCollection: () => of(new RestResponse(true, 200, 'OK')),
removeMappingFromCollection: () => of(new RestResponse(true, 200, 'OK')),
mapToCollection: () => createSuccessfulRemoteDataObject$({}),
removeMappingFromCollection: () => createSuccessfulRemoteDataObject$({}),
getMappedCollections: () => of(mockCollectionsRD),
/* tslint:disable:no-empty */
clearMappedCollectionsRequests: () => {}
@@ -143,7 +147,7 @@ describe('ItemCollectionMapperComponent', () => {
});
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);
expect(notificationsService.success).not.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', () => {
spyOn(itemDataService, 'removeMappingFromCollection').and.returnValue(of(new RestResponse(false, 404, 'Not Found')));
spyOn(itemDataService, 'removeMappingFromCollection').and.returnValue(createFailedRemoteDataObject$('Not Found', 404));
comp.removeMappings(ids);
expect(notificationsService.success).not.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 { fadeIn, fadeInOut } from '../../../shared/animations/fade';
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 { Item } from '../../../core/shared/item.model';
import {
getFirstSucceededRemoteDataPayload,
getRemoteDataPayload,
getSucceededRemoteData,
getFirstSucceededRemoteData,
toDSpaceObjectListRD
} from '../../../core/shared/operators';
import { ActivatedRoute, Router } from '@angular/router';
@@ -20,11 +20,11 @@ import { TranslateService } from '@ngx-translate/core';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
import { isNotEmpty } from '../../../shared/empty.util';
import { RestResponse } from '../../../core/cache/response.models';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
import { SearchService } from '../../../core/shared/search/search.service';
import { NoContent } from '../../../core/shared/NoContent.model';
@Component({
selector: 'ds-item-collection-mapper',
@@ -92,7 +92,7 @@ export class ItemCollectionMapperComponent implements OnInit {
}
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.loadCollectionLists();
}
@@ -141,13 +141,13 @@ export class ItemCollectionMapperComponent implements OnInit {
mapCollections(ids: string[]) {
const itemIdAndExcludingIds$ = observableCombineLatest(
this.itemRD$.pipe(
getSucceededRemoteData(),
getFirstSucceededRemoteData(),
take(1),
map((rd: RemoteData<Item>) => rd.payload),
map((item: Item) => item.id)
),
this.itemCollectionsRD$.pipe(
getSucceededRemoteData(),
getFirstSucceededRemoteData(),
take(1),
map((rd: RemoteData<PaginatedList<Collection>>) => rd.payload.page),
map((collections: Collection[]) => collections.map((collection: Collection) => collection.id))
@@ -168,7 +168,7 @@ export class ItemCollectionMapperComponent implements OnInit {
*/
removeMappings(ids: string[]) {
const responses$ = this.itemRD$.pipe(
getSucceededRemoteData(),
getFirstSucceededRemoteData(),
map((itemRD: RemoteData<Item>) => itemRD.payload.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 {string} messagePrefix The prefix to build the notification messages with
*/
private showNotifications(responses$: Observable<RestResponse[]>, messagePrefix: string) {
responses$.subscribe((responses: RestResponse[]) => {
const successful = responses.filter((response: RestResponse) => response.isSuccessful);
const unsuccessful = responses.filter((response: RestResponse) => !response.isSuccessful);
private showNotifications(responses$: Observable<Array<RemoteData<NoContent>>>, messagePrefix: string) {
responses$.subscribe((responses: Array<RemoteData<NoContent>>) => {
const successful = responses.filter((response: RemoteData<NoContent>) => response.hasSucceeded);
const unsuccessful = responses.filter((response: RemoteData<NoContent>) => response.hasFailed);
if (successful.length > 0) {
const successMessages = observableCombineLatest(
this.translateService.get(`${messagePrefix}.success.head`),
@@ -280,7 +280,7 @@ export class ItemCollectionMapperComponent implements OnInit {
*/
onCancel() {
this.itemRD$.pipe(
getSucceededRemoteData(),
getFirstSucceededRemoteData(),
getRemoteDataPayload(),
take(1)
).subscribe((item: Item) => {

View File

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

View File

@@ -1,12 +1,21 @@
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 { 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 { VirtualMetadata } from '../virtual-metadata/virtual-metadata.component';
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 { Item } from '../../../core/shared/item.model';
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 { LinkService } from '../../../core/cache/builders/link.service';
import { followLink } from '../../../shared/utils/follow-link-config.model';
import { RestResponse } from '../../../core/cache/response.models';
import { getItemEditRoute } from '../../item-page-routing-paths';
import { RemoteData } from '../../../core/data/remote-data';
import { NoContent } from '../../../core/shared/NoContent.model';
@Component({
selector: 'ds-item-delete',
@@ -105,10 +115,10 @@ export class ItemDeleteComponent
const label = this.item.firstMetadataValue('relationship.type');
if (label !== undefined) {
this.types$ = this.entityTypeService.getEntityTypeByLabel(label).pipe(
getSucceededRemoteData(),
getFirstSucceededRemoteData(),
getRemoteDataPayload(),
switchMap((entityType) => this.entityTypeService.getEntityTypeRelationships(entityType.id)),
getSucceededRemoteData(),
getFirstSucceededRemoteData(),
getRemoteDataPayload(),
map((relationshipTypes) => relationshipTypes.page),
switchMap((types) =>
@@ -220,7 +230,7 @@ export class ItemDeleteComponent
followLink('rightItem'),
);
return relationship.relationshipType.pipe(
getSucceededRemoteData(),
getFirstSucceededRemoteData(),
getRemoteDataPayload(),
filter((relationshipType: RelationshipType) => hasValue(relationshipType) && isNotEmpty(relationshipType.uuid))
);
@@ -238,7 +248,7 @@ export class ItemDeleteComponent
relationship,
this.isLeftItem(relationship).pipe(
switchMap((isLeftItem) => isLeftItem ? relationship.rightItem : relationship.leftItem),
getSucceededRemoteData(),
getFirstSucceededRemoteData(),
getRemoteDataPayload(),
),
);
@@ -285,7 +295,7 @@ export class ItemDeleteComponent
private isLeftItem(relationship: Relationship): Observable<boolean> {
return relationship.leftItem.pipe(
getSucceededRemoteData(),
getFirstSucceededRemoteData(),
getRemoteDataPayload(),
filter((item: Item) => hasValue(item) && isNotEmpty(item.uuid)),
map((leftItem) => leftItem.uuid === this.item.uuid)
@@ -327,9 +337,9 @@ export class ItemDeleteComponent
)
),
).subscribe((types) => {
this.itemDataService.delete(this.item.id, types).pipe(first()).subscribe(
(response: RestResponse) => {
this.notify(response.isSuccessful);
this.itemDataService.delete(this.item.id, types).pipe(getFirstCompletedRemoteData()).subscribe(
(rd: RemoteData<NoContent>) => {
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 { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
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 { MetadataSchema } from '../../../../core/metadata/metadata-schema.model';
import { RegistryService } from '../../../../core/registry/registry.service';
@@ -66,7 +66,7 @@ describe('EditInPlaceFieldComponent', () => {
beforeEach(async(() => {
scheduler = getTestScheduler();
paginatedMetadataFields = new PaginatedList(undefined, [mdField1, mdField2, mdField3]);
paginatedMetadataFields = buildPaginatedList(undefined, [mdField1, mdField2, mdField3]);
metadataFieldService = jasmine.createSpyObj({
queryMetadataFields: createSuccessfulRemoteDataObject$(paginatedMetadataFields),
@@ -221,7 +221,7 @@ describe('EditInPlaceFieldComponent', () => {
}));
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', () => {

View File

@@ -1,10 +1,13 @@
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 { RegistryService } from '../../../../core/registry/registry.service';
import { cloneDeep } from 'lodash';
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 { FieldUpdate } from '../../../../core/data/object-updates/object-updates.reducer';
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
@@ -124,10 +127,10 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges {
*/
findMetadataFieldSuggestions(query: string) {
if (isNotEmpty(query)) {
return this.registryService.queryMetadataFields(query, null, followLink('schema')).pipe(
return this.registryService.queryMetadataFields(query, null, false, followLink('schema')).pipe(
getFirstSucceededRemoteData(),
metadataFieldsToString(),
take(1))
.subscribe((fieldNames: string[]) => {
).subscribe((fieldNames: string[]) => {
this.setInputSuggestions(fieldNames);
})
} 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 { MetadatumViewModel } from '../../../core/shared/metadata.models';
import { RegistryService } from '../../../core/registry/registry.service';
import { PaginatedList } from '../../../core/data/paginated-list';
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
import { MetadataField } from '../../../core/metadata/metadata-field.model';
import {
@@ -30,6 +29,7 @@ import {
} from '../../../shared/remote-data.utils';
import { ObjectCacheService } from '../../../core/cache/object-cache.service';
import { DSOSuccessResponse } from '../../../core/cache/response.models';
import { createPaginatedList } from '../../../shared/testing/utils.test';
let comp: any;
let fixture: ComponentFixture<ItemMetadataComponent>;
@@ -133,7 +133,7 @@ describe('ItemMetadataComponent', () => {
data: observableOf({ dso: createSuccessfulRemoteDataObject(item) })
}
};
paginatedMetadataFields = new PaginatedList(undefined, [mdField1, mdField2, mdField3]);
paginatedMetadataFields = createPaginatedList([mdField1, mdField2, mdField3]);
metadataFieldService = jasmine.createSpyObj({
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 { ActivatedRoute, Router } from '@angular/router';
import { cloneDeep } from 'lodash';
import { first, switchMap, tap } from 'rxjs/operators';
import { getSucceededRemoteData } from '../../../core/shared/operators';
import { first, switchMap } from 'rxjs/operators';
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
import { RemoteData } from '../../../core/data/remote-data';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
import { MetadataValue, MetadatumViewModel } from '../../../core/shared/metadata.models';
import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component';
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 { Operation } from 'fast-json-patch';
import { METADATA_PATCH_OPERATION_SERVICE_TOKEN } from '../../../core/data/object-updates/patch-operation-service/metadata-patch-operation.service';
import { DSOSuccessResponse, ErrorResponse } from '../../../core/cache/response.models';
import { MetadataPatchOperationService } from '../../../core/data/object-updates/patch-operation-service/metadata-patch-operation.service';
@Component({
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
*/
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,27 +100,21 @@ export class ItemMetadataComponent extends AbstractItemUpdateComponent {
first(),
switchMap((patch: Operation[]) => {
return this.updateService.patch(this.item, patch).pipe(
tap((response) => {
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()
getFirstCompletedRemoteData()
);
})
).subscribe(
(rd: RemoteData<Item>) => {
if (rd.hasFailed) {
this.notificationsService.error(this.getNotificationTitle('error'), rd.errorMessage);
} else {
this.item = rd.payload;
this.checkAndFixMetadataUUIDs();
this.initializeOriginalFields();
this.updates$ = this.objectUpdatesService.getFieldUpdates(this.url, this.item.metadataAsList);
this.notificationsService.success(this.getNotificationTitle('saved'), this.getNotificationContent('saved'));
}
}
)
} else {
this.notificationsService.error(this.getNotificationTitle('invalid'), this.getNotificationContent('invalid'));

View File

@@ -7,10 +7,7 @@ import { RouterTestingModule } from '@angular/router/testing';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs';
import { RestResponse } from '../../../core/cache/response.models';
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 { Item } from '../../../core/shared/item.model';
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 { RouterStub } from '../../../shared/testing/router.stub';
import { ItemMoveComponent } from './item-move.component';
import {
createFailedRemoteDataObject$,
createSuccessfulRemoteDataObject,
createSuccessfulRemoteDataObject$
} from '../../../shared/remote-data.utils';
import { createPaginatedList } from '../../../shared/testing/utils.test';
describe('ItemMoveComponent', () => {
let comp: ItemMoveComponent;
@@ -34,22 +37,6 @@ describe('ItemMoveComponent', () => {
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(), {
uuid: 'collection-uuid-1',
name: 'Test collection 1'
@@ -60,10 +47,25 @@ describe('ItemMoveComponent', () => {
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 = {
search: () => {
return observableOf(new RemoteData(false, false, true, null,
new PaginatedList(null, [
return createSuccessfulRemoteDataObject$(createPaginatedList([
{
indexableObject: collection1,
hitHighlights: {}
@@ -71,7 +73,7 @@ describe('ItemMoveComponent', () => {
indexableObject: collection2,
hitHighlights: {}
}
])));
]));
}
};

View File

@@ -3,15 +3,17 @@ import { first, map } from 'rxjs/operators';
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
import { RemoteData } from '../../../core/data/remote-data';
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 { ActivatedRoute, Router } from '@angular/router';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
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 { Observable, of as observableOf } from 'rxjs';
import { RestResponse } from '../../../core/cache/response.models';
import { Collection } from '../../../core/shared/collection.model';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { SearchService } from '../../../core/shared/search/search.service';
@@ -55,7 +57,7 @@ export class ItemMoveComponent implements OnInit {
}
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.itemId = rd.payload.id;
}
@@ -114,10 +116,10 @@ export class ItemMoveComponent implements OnInit {
*/
moveCollection() {
this.processing = true;
this.itemDataService.moveToCollection(this.itemId, this.selectedCollection).pipe(first()).subscribe(
(response: RestResponse) => {
this.itemDataService.moveToCollection(this.itemId, this.selectedCollection).pipe(getFirstCompletedRemoteData()).subscribe(
(response: RemoteData<Collection>) => {
this.router.navigate([getItemEditRoute(this.itemId)]);
if (response.isSuccessful) {
if (response.hasSucceeded) {
this.notificationsService.success(this.translateService.get('item.edit.move.success'));
} else {
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 { RouterStub } from '../../../shared/testing/router.stub';
import { of as observableOf } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data';
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
@@ -16,7 +15,7 @@ import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
import { ItemPrivateComponent } from './item-private.component';
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 fixture: ComponentFixture<ItemPrivateComponent>;
@@ -46,14 +45,12 @@ describe('ItemPrivateComponent', () => {
});
mockItemDataService = jasmine.createSpyObj('mockItemDataService', {
setDiscoverable: observableOf(new RestResponse(true, 200, 'OK'))
setDiscoverable: createSuccessfulRemoteDataObject$(mockItem)
});
routeStub = {
data: observableOf({
dso: createSuccessfulRemoteDataObject({
id: 'fake-id'
})
dso: createSuccessfulRemoteDataObject(mockItem)
})
};
@@ -98,9 +95,8 @@ describe('ItemPrivateComponent', () => {
spyOn(comp, 'processRestResponse');
comp.performAction();
expect(mockItemDataService.setDiscoverable).toHaveBeenCalledWith(mockItem.id, false);
expect(mockItemDataService.setDiscoverable).toHaveBeenCalledWith(mockItem, false);
expect(comp.processRestResponse).toHaveBeenCalled();
});
});
})
;
});

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