mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge branch 'w2p-117573_remove-observable-function-calls-from-template-7.6' into dspace-7_x
# Conflicts: # src/app/access-control/epeople-registry/epeople-registry.component.html # src/app/access-control/epeople-registry/epeople-registry.component.ts # src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html # src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts # src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts # src/app/access-control/group-registry/group-form/group-form.component.html # src/app/access-control/group-registry/group-form/group-form.component.ts # src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.html # src/app/admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts # src/app/admin/admin-registries/metadata-registry/metadata-schema-form/metadata-schema-form.component.ts # src/app/admin/admin-registries/metadata-schema/metadata-schema.component.html # src/app/admin/admin-registries/metadata-schema/metadata-schema.component.ts # src/app/shared/pagination/pagination.component.ts
This commit is contained in:
@@ -61,7 +61,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let epersonDto of (ePeopleDto$ | async)?.page"
|
<tr *ngFor="let epersonDto of (ePeopleDto$ | async)?.page"
|
||||||
[ngClass]="{'table-primary' : isActive(epersonDto.eperson) | async}">
|
[ngClass]="{'table-primary' : (activeEPerson$ | async) === epersonDto.eperson}">
|
||||||
<td>{{epersonDto.eperson.id}}</td>
|
<td>{{epersonDto.eperson.id}}</td>
|
||||||
<td>{{ dsoNameService.getName(epersonDto.eperson) }}</td>
|
<td>{{ dsoNameService.getName(epersonDto.eperson) }}</td>
|
||||||
<td>{{epersonDto.eperson.email}}</td>
|
<td>{{epersonDto.eperson.email}}</td>
|
||||||
|
@@ -46,6 +46,8 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
ePeopleDto$: BehaviorSubject<PaginatedList<EpersonDtoModel>> = new BehaviorSubject<PaginatedList<EpersonDtoModel>>({} as any);
|
ePeopleDto$: BehaviorSubject<PaginatedList<EpersonDtoModel>> = new BehaviorSubject<PaginatedList<EpersonDtoModel>>({} as any);
|
||||||
|
|
||||||
|
activeEPerson$: Observable<EPerson>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An observable for the pageInfo, needed to pass to the pagination component
|
* An observable for the pageInfo, needed to pass to the pagination component
|
||||||
*/
|
*/
|
||||||
@@ -111,6 +113,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
|
|||||||
initialisePage() {
|
initialisePage() {
|
||||||
this.searching$.next(true);
|
this.searching$.next(true);
|
||||||
this.search({scope: this.currentSearchScope, query: this.currentSearchQuery});
|
this.search({scope: this.currentSearchScope, query: this.currentSearchQuery});
|
||||||
|
this.activeEPerson$ = this.epersonService.getActiveEPerson();
|
||||||
this.subs.push(this.ePeople$.pipe(
|
this.subs.push(this.ePeople$.pipe(
|
||||||
switchMap((epeople: PaginatedList<EPerson>) => {
|
switchMap((epeople: PaginatedList<EPerson>) => {
|
||||||
if (epeople.pageInfo.totalElements > 0) {
|
if (epeople.pageInfo.totalElements > 0) {
|
||||||
@@ -178,23 +181,6 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether the given EPerson is active (being edited)
|
|
||||||
* @param eperson
|
|
||||||
*/
|
|
||||||
isActive(eperson: EPerson): Observable<boolean> {
|
|
||||||
return this.getActiveEPerson().pipe(
|
|
||||||
map((activeEPerson) => eperson === activeEPerson)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the active eperson (being edited)
|
|
||||||
*/
|
|
||||||
getActiveEPerson(): Observable<EPerson> {
|
|
||||||
return this.epersonService.getActiveEPerson();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes EPerson, show notification on success/failure & updates EPeople list
|
* Deletes EPerson, show notification on success/failure & updates EPeople list
|
||||||
*/
|
*/
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<div class="group-form row">
|
<div class="group-form row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
|
||||||
<div *ngIf="epersonService.getActiveEPerson() | async; then editHeader; else createHeader"></div>
|
<div *ngIf="activeEPerson$ | async; then editHeader; else createHeader"></div>
|
||||||
|
|
||||||
<ng-template #createHeader>
|
<ng-template #createHeader>
|
||||||
<h1 class="border-bottom pb-2">{{messagePrefix + '.create' | translate}}</h1>
|
<h1 class="border-bottom pb-2">{{messagePrefix + '.create' | translate}}</h1>
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
<ds-themed-loading [showMessage]="false" *ngIf="!formGroup"></ds-themed-loading>
|
<ds-themed-loading [showMessage]="false" *ngIf="!formGroup"></ds-themed-loading>
|
||||||
|
|
||||||
<div *ngIf="epersonService.getActiveEPerson() | async">
|
<div *ngIf="activeEPerson$ | async">
|
||||||
<h2>{{messagePrefix + '.groupsEPersonIsMemberOf' | translate}}</h2>
|
<h2>{{messagePrefix + '.groupsEPersonIsMemberOf' | translate}}</h2>
|
||||||
|
|
||||||
<ds-themed-loading [showMessage]="false" *ngIf="!(groups | async)"></ds-themed-loading>
|
<ds-themed-loading [showMessage]="false" *ngIf="!(groups | async)"></ds-themed-loading>
|
||||||
@@ -75,7 +75,9 @@
|
|||||||
{{ dsoNameService.getName(group) }}
|
{{ dsoNameService.getName(group) }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle">{{ dsoNameService.getName(undefined) }}</td>
|
<td class="align-middle">
|
||||||
|
{{ dsoNameService.getName((group.object | async)?.payload) }}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import { Observable, of as observableOf } from 'rxjs';
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { UntypedFormControl, UntypedFormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
import { UntypedFormControl, UntypedFormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
import { BrowserModule, By } from '@angular/platform-browser';
|
import { BrowserModule, By } from '@angular/platform-browser';
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { buildPaginatedList, PaginatedList } from '../../../core/data/paginated-list.model';
|
import { buildPaginatedList, PaginatedList } from '../../../core/data/paginated-list.model';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
|
import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
|
||||||
@@ -19,7 +19,6 @@ import { EPersonMock, EPersonMock2 } from '../../../shared/testing/eperson.mock'
|
|||||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
import { getMockFormBuilderService } from '../../../shared/mocks/form-builder-service.mock';
|
import { getMockFormBuilderService } from '../../../shared/mocks/form-builder-service.mock';
|
||||||
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
|
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
|
||||||
import { TranslateLoaderMock } from '../../../shared/mocks/translate-loader.mock';
|
|
||||||
import { AuthService } from '../../../core/auth/auth.service';
|
import { AuthService } from '../../../core/auth/auth.service';
|
||||||
import { AuthServiceStub } from '../../../shared/testing/auth-service.stub';
|
import { AuthServiceStub } from '../../../shared/testing/auth-service.stub';
|
||||||
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
||||||
@@ -35,6 +34,7 @@ import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model
|
|||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { RouterStub } from '../../../shared/testing/router.stub';
|
import { RouterStub } from '../../../shared/testing/router.stub';
|
||||||
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
|
||||||
describe('EPersonFormComponent', () => {
|
describe('EPersonFormComponent', () => {
|
||||||
let component: EPersonFormComponent;
|
let component: EPersonFormComponent;
|
||||||
@@ -59,9 +59,6 @@ describe('EPersonFormComponent', () => {
|
|||||||
ePersonDataServiceStub = {
|
ePersonDataServiceStub = {
|
||||||
activeEPerson: null,
|
activeEPerson: null,
|
||||||
allEpeople: mockEPeople,
|
allEpeople: mockEPeople,
|
||||||
getEPeople(): Observable<RemoteData<PaginatedList<EPerson>>> {
|
|
||||||
return createSuccessfulRemoteDataObject$(buildPaginatedList(null, this.allEpeople));
|
|
||||||
},
|
|
||||||
getActiveEPerson(): Observable<EPerson> {
|
getActiveEPerson(): Observable<EPerson> {
|
||||||
return observableOf(this.activeEPerson);
|
return observableOf(this.activeEPerson);
|
||||||
},
|
},
|
||||||
@@ -195,12 +192,8 @@ describe('EPersonFormComponent', () => {
|
|||||||
router = new RouterStub();
|
router = new RouterStub();
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
|
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
|
||||||
TranslateModule.forRoot({
|
RouterTestingModule,
|
||||||
loader: {
|
TranslateModule.forRoot(),
|
||||||
provide: TranslateLoader,
|
|
||||||
useClass: TranslateLoaderMock
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
declarations: [EPersonFormComponent],
|
declarations: [EPersonFormComponent],
|
||||||
providers: [
|
providers: [
|
||||||
@@ -217,7 +210,7 @@ describe('EPersonFormComponent', () => {
|
|||||||
{ provide: Router, useValue: router },
|
{ provide: Router, useValue: router },
|
||||||
EPeopleRegistryComponent
|
EPeopleRegistryComponent
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -236,37 +229,13 @@ describe('EPersonFormComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('check form validation', () => {
|
describe('check form validation', () => {
|
||||||
let firstName;
|
let canLogIn: boolean;
|
||||||
let lastName;
|
let requireCertificate: boolean;
|
||||||
let email;
|
|
||||||
let canLogIn;
|
|
||||||
let requireCertificate;
|
|
||||||
|
|
||||||
let expected;
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
firstName = 'testName';
|
|
||||||
lastName = 'testLastName';
|
|
||||||
email = 'testEmail@test.com';
|
|
||||||
canLogIn = false;
|
canLogIn = false;
|
||||||
requireCertificate = false;
|
requireCertificate = false;
|
||||||
|
|
||||||
expected = Object.assign(new EPerson(), {
|
|
||||||
metadata: {
|
|
||||||
'eperson.firstname': [
|
|
||||||
{
|
|
||||||
value: firstName
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'eperson.lastname': [
|
|
||||||
{
|
|
||||||
value: lastName
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
email: email,
|
|
||||||
canLogIn: canLogIn,
|
|
||||||
requireCertificate: requireCertificate,
|
|
||||||
});
|
|
||||||
spyOn(component.submitForm, 'emit');
|
spyOn(component.submitForm, 'emit');
|
||||||
component.canLogIn.value = canLogIn;
|
component.canLogIn.value = canLogIn;
|
||||||
component.requireCertificate.value = requireCertificate;
|
component.requireCertificate.value = requireCertificate;
|
||||||
@@ -340,15 +309,13 @@ describe('EPersonFormComponent', () => {
|
|||||||
expect(component.formGroup.controls.email.errors.emailTaken).toBeTruthy();
|
expect(component.formGroup.controls.email.errors.emailTaken).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when submitting the form', () => {
|
describe('when submitting the form', () => {
|
||||||
let firstName;
|
let firstName;
|
||||||
let lastName;
|
let lastName;
|
||||||
let email;
|
let email;
|
||||||
let canLogIn;
|
let canLogIn: boolean;
|
||||||
let requireCertificate;
|
let requireCertificate;
|
||||||
|
|
||||||
let expected;
|
let expected;
|
||||||
@@ -377,6 +344,7 @@ describe('EPersonFormComponent', () => {
|
|||||||
requireCertificate: requireCertificate,
|
requireCertificate: requireCertificate,
|
||||||
});
|
});
|
||||||
spyOn(component.submitForm, 'emit');
|
spyOn(component.submitForm, 'emit');
|
||||||
|
component.ngOnInit();
|
||||||
component.firstName.value = firstName;
|
component.firstName.value = firstName;
|
||||||
component.lastName.value = lastName;
|
component.lastName.value = lastName;
|
||||||
component.email.value = email;
|
component.email.value = email;
|
||||||
@@ -416,9 +384,17 @@ describe('EPersonFormComponent', () => {
|
|||||||
email: email,
|
email: email,
|
||||||
canLogIn: canLogIn,
|
canLogIn: canLogIn,
|
||||||
requireCertificate: requireCertificate,
|
requireCertificate: requireCertificate,
|
||||||
_links: undefined
|
_links: {
|
||||||
|
groups: {
|
||||||
|
href: '',
|
||||||
|
},
|
||||||
|
self: {
|
||||||
|
href: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
spyOn(ePersonDataServiceStub, 'getActiveEPerson').and.returnValue(observableOf(expectedWithId));
|
spyOn(ePersonDataServiceStub, 'getActiveEPerson').and.returnValue(observableOf(expectedWithId));
|
||||||
|
component.ngOnInit();
|
||||||
component.onSubmit();
|
component.onSubmit();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
@@ -466,22 +442,19 @@ describe('EPersonFormComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('delete', () => {
|
describe('delete', () => {
|
||||||
|
|
||||||
let ePersonId;
|
|
||||||
let eperson: EPerson;
|
let eperson: EPerson;
|
||||||
let modalService;
|
let modalService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(authService, 'impersonate').and.callThrough();
|
spyOn(authService, 'impersonate').and.callThrough();
|
||||||
ePersonId = 'testEPersonId';
|
|
||||||
eperson = EPersonMock;
|
eperson = EPersonMock;
|
||||||
component.epersonInitial = eperson;
|
component.epersonInitial = eperson;
|
||||||
component.canDelete$ = observableOf(true);
|
component.canDelete$ = observableOf(true);
|
||||||
spyOn(component.epersonService, 'getActiveEPerson').and.returnValue(observableOf(eperson));
|
spyOn(component.epersonService, 'getActiveEPerson').and.returnValue(observableOf(eperson));
|
||||||
modalService = (component as any).modalService;
|
modalService = (component as any).modalService;
|
||||||
spyOn(modalService, 'open').and.returnValue(Object.assign({ componentInstance: Object.assign({ response: observableOf(true) }) }));
|
spyOn(modalService, 'open').and.returnValue(Object.assign({ componentInstance: Object.assign({ response: observableOf(true) }) }));
|
||||||
|
component.ngOnInit();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('the delete button should be visible if the ePerson can be deleted', () => {
|
it('the delete button should be visible if the ePerson can be deleted', () => {
|
||||||
|
@@ -139,6 +139,11 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
canImpersonate$: Observable<boolean>;
|
canImpersonate$: Observable<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current {@link EPerson}
|
||||||
|
*/
|
||||||
|
activeEPerson$: Observable<EPerson>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of subscriptions
|
* List of subscriptions
|
||||||
*/
|
*/
|
||||||
@@ -199,7 +204,11 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
protected route: ActivatedRoute,
|
protected route: ActivatedRoute,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
) {
|
) {
|
||||||
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.activeEPerson$ = this.epersonService.getActiveEPerson();
|
||||||
|
this.subs.push(this.activeEPerson$.subscribe((eperson: EPerson) => {
|
||||||
this.epersonInitial = eperson;
|
this.epersonInitial = eperson;
|
||||||
if (hasValue(eperson)) {
|
if (hasValue(eperson)) {
|
||||||
this.isImpersonated = this.authService.isImpersonatingUser(eperson.id);
|
this.isImpersonated = this.authService.isImpersonatingUser(eperson.id);
|
||||||
@@ -207,9 +216,6 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
this.submitLabel = 'form.submit';
|
this.submitLabel = 'form.submit';
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.initialisePage();
|
this.initialisePage();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,123 +226,112 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
this.subs.push(this.epersonService.findById(this.route.snapshot.params.id).subscribe((ePersonRD: RemoteData<EPerson>) => {
|
this.subs.push(this.epersonService.findById(this.route.snapshot.params.id).subscribe((ePersonRD: RemoteData<EPerson>) => {
|
||||||
this.epersonService.editEPerson(ePersonRD.payload);
|
this.epersonService.editEPerson(ePersonRD.payload);
|
||||||
}));
|
}));
|
||||||
observableCombineLatest([
|
this.firstName = new DynamicInputModel({
|
||||||
this.translateService.get(`${this.messagePrefix}.firstName`),
|
id: 'firstName',
|
||||||
this.translateService.get(`${this.messagePrefix}.lastName`),
|
label: this.translateService.instant(`${this.messagePrefix}.firstName`),
|
||||||
this.translateService.get(`${this.messagePrefix}.email`),
|
name: 'firstName',
|
||||||
this.translateService.get(`${this.messagePrefix}.canLogIn`),
|
validators: {
|
||||||
this.translateService.get(`${this.messagePrefix}.requireCertificate`),
|
required: null,
|
||||||
this.translateService.get(`${this.messagePrefix}.emailHint`),
|
},
|
||||||
]).subscribe(([firstName, lastName, email, canLogIn, requireCertificate, emailHint]) => {
|
required: true,
|
||||||
this.firstName = new DynamicInputModel({
|
|
||||||
id: 'firstName',
|
|
||||||
label: firstName,
|
|
||||||
name: 'firstName',
|
|
||||||
validators: {
|
|
||||||
required: null,
|
|
||||||
},
|
|
||||||
required: true,
|
|
||||||
});
|
|
||||||
this.lastName = new DynamicInputModel({
|
|
||||||
id: 'lastName',
|
|
||||||
label: lastName,
|
|
||||||
name: 'lastName',
|
|
||||||
validators: {
|
|
||||||
required: null,
|
|
||||||
},
|
|
||||||
required: true,
|
|
||||||
});
|
|
||||||
this.email = new DynamicInputModel({
|
|
||||||
id: 'email',
|
|
||||||
label: email,
|
|
||||||
name: 'email',
|
|
||||||
validators: {
|
|
||||||
required: null,
|
|
||||||
pattern: '^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$',
|
|
||||||
},
|
|
||||||
required: true,
|
|
||||||
errorMessages: {
|
|
||||||
emailTaken: 'error.validation.emailTaken',
|
|
||||||
pattern: 'error.validation.NotValidEmail'
|
|
||||||
},
|
|
||||||
hint: emailHint
|
|
||||||
});
|
|
||||||
this.canLogIn = new DynamicCheckboxModel(
|
|
||||||
{
|
|
||||||
id: 'canLogIn',
|
|
||||||
label: canLogIn,
|
|
||||||
name: 'canLogIn',
|
|
||||||
value: (this.epersonInitial != null ? this.epersonInitial.canLogIn : true)
|
|
||||||
});
|
|
||||||
this.requireCertificate = new DynamicCheckboxModel(
|
|
||||||
{
|
|
||||||
id: 'requireCertificate',
|
|
||||||
label: requireCertificate,
|
|
||||||
name: 'requireCertificate',
|
|
||||||
value: (this.epersonInitial != null ? this.epersonInitial.requireCertificate : false)
|
|
||||||
});
|
|
||||||
this.formModel = [
|
|
||||||
this.firstName,
|
|
||||||
this.lastName,
|
|
||||||
this.email,
|
|
||||||
this.canLogIn,
|
|
||||||
this.requireCertificate,
|
|
||||||
];
|
|
||||||
this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
|
|
||||||
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
|
|
||||||
if (eperson != null) {
|
|
||||||
this.groups = this.groupsDataService.findListByHref(eperson._links.groups.href, {
|
|
||||||
currentPage: 1,
|
|
||||||
elementsPerPage: this.config.pageSize
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.formGroup.patchValue({
|
|
||||||
firstName: eperson != null ? eperson.firstMetadataValue('eperson.firstname') : '',
|
|
||||||
lastName: eperson != null ? eperson.firstMetadataValue('eperson.lastname') : '',
|
|
||||||
email: eperson != null ? eperson.email : '',
|
|
||||||
canLogIn: eperson != null ? eperson.canLogIn : true,
|
|
||||||
requireCertificate: eperson != null ? eperson.requireCertificate : false
|
|
||||||
});
|
|
||||||
|
|
||||||
if (eperson === null && !!this.formGroup.controls.email) {
|
|
||||||
this.formGroup.controls.email.setAsyncValidators(ValidateEmailNotTaken.createValidator(this.epersonService));
|
|
||||||
this.emailValueChangeSubscribe = this.email.valueChanges.pipe(debounceTime(300)).subscribe(() => {
|
|
||||||
this.changeDetectorRef.detectChanges();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
const activeEPerson$ = this.epersonService.getActiveEPerson();
|
|
||||||
|
|
||||||
this.groups = activeEPerson$.pipe(
|
|
||||||
switchMap((eperson) => {
|
|
||||||
return observableCombineLatest([observableOf(eperson), this.paginationService.getFindListOptions(this.config.id, {
|
|
||||||
currentPage: 1,
|
|
||||||
elementsPerPage: this.config.pageSize
|
|
||||||
})]);
|
|
||||||
}),
|
|
||||||
switchMap(([eperson, findListOptions]) => {
|
|
||||||
if (eperson != null) {
|
|
||||||
return this.groupsDataService.findListByHref(eperson._links.groups.href, findListOptions, true, true, followLink('object'));
|
|
||||||
}
|
|
||||||
return observableOf(undefined);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
this.canImpersonate$ = activeEPerson$.pipe(
|
|
||||||
switchMap((eperson) => {
|
|
||||||
if (hasValue(eperson)) {
|
|
||||||
return this.authorizationService.isAuthorized(FeatureID.LoginOnBehalfOf, eperson.self);
|
|
||||||
} else {
|
|
||||||
return observableOf(false);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
this.canDelete$ = activeEPerson$.pipe(
|
|
||||||
switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined))
|
|
||||||
);
|
|
||||||
this.canReset$ = observableOf(true);
|
|
||||||
});
|
});
|
||||||
|
this.lastName = new DynamicInputModel({
|
||||||
|
id: 'lastName',
|
||||||
|
label: this.translateService.instant(`${this.messagePrefix}.lastName`),
|
||||||
|
name: 'lastName',
|
||||||
|
validators: {
|
||||||
|
required: null,
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
this.email = new DynamicInputModel({
|
||||||
|
id: 'email',
|
||||||
|
label: this.translateService.instant(`${this.messagePrefix}.email`),
|
||||||
|
name: 'email',
|
||||||
|
validators: {
|
||||||
|
required: null,
|
||||||
|
pattern: '^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$',
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
errorMessages: {
|
||||||
|
emailTaken: 'error.validation.emailTaken',
|
||||||
|
pattern: 'error.validation.NotValidEmail'
|
||||||
|
},
|
||||||
|
hint: this.translateService.instant(`${this.messagePrefix}.emailHint`),
|
||||||
|
});
|
||||||
|
this.canLogIn = new DynamicCheckboxModel(
|
||||||
|
{
|
||||||
|
id: 'canLogIn',
|
||||||
|
label: this.translateService.instant(`${this.messagePrefix}.canLogIn`),
|
||||||
|
name: 'canLogIn',
|
||||||
|
value: (this.epersonInitial != null ? this.epersonInitial.canLogIn : true)
|
||||||
|
});
|
||||||
|
this.requireCertificate = new DynamicCheckboxModel(
|
||||||
|
{
|
||||||
|
id: 'requireCertificate',
|
||||||
|
label: this.translateService.instant(`${this.messagePrefix}.requireCertificate`),
|
||||||
|
name: 'requireCertificate',
|
||||||
|
value: (this.epersonInitial != null ? this.epersonInitial.requireCertificate : false)
|
||||||
|
});
|
||||||
|
this.formModel = [
|
||||||
|
this.firstName,
|
||||||
|
this.lastName,
|
||||||
|
this.email,
|
||||||
|
this.canLogIn,
|
||||||
|
this.requireCertificate,
|
||||||
|
];
|
||||||
|
this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
|
||||||
|
this.subs.push(this.activeEPerson$.subscribe((eperson: EPerson) => {
|
||||||
|
if (eperson != null) {
|
||||||
|
this.groups = this.groupsDataService.findListByHref(eperson._links.groups.href, {
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: this.config.pageSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.formGroup.patchValue({
|
||||||
|
firstName: eperson != null ? eperson.firstMetadataValue('eperson.firstname') : '',
|
||||||
|
lastName: eperson != null ? eperson.firstMetadataValue('eperson.lastname') : '',
|
||||||
|
email: eperson != null ? eperson.email : '',
|
||||||
|
canLogIn: eperson != null ? eperson.canLogIn : true,
|
||||||
|
requireCertificate: eperson != null ? eperson.requireCertificate : false
|
||||||
|
});
|
||||||
|
|
||||||
|
if (eperson === null && !!this.formGroup.controls.email) {
|
||||||
|
this.formGroup.controls.email.setAsyncValidators(ValidateEmailNotTaken.createValidator(this.epersonService));
|
||||||
|
this.emailValueChangeSubscribe = this.email.valueChanges.pipe(debounceTime(300)).subscribe(() => {
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.groups = this.activeEPerson$.pipe(
|
||||||
|
switchMap((eperson) => {
|
||||||
|
return observableCombineLatest([observableOf(eperson), this.paginationService.getFindListOptions(this.config.id, {
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: this.config.pageSize
|
||||||
|
})]);
|
||||||
|
}),
|
||||||
|
switchMap(([eperson, findListOptions]) => {
|
||||||
|
if (eperson != null) {
|
||||||
|
return this.groupsDataService.findListByHref(eperson._links.groups.href, findListOptions, true, true, followLink('object'));
|
||||||
|
}
|
||||||
|
return observableOf(undefined);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.canImpersonate$ = this.activeEPerson$.pipe(
|
||||||
|
switchMap((eperson) => {
|
||||||
|
if (hasValue(eperson)) {
|
||||||
|
return this.authorizationService.isAuthorized(FeatureID.LoginOnBehalfOf, eperson.self);
|
||||||
|
} else {
|
||||||
|
return observableOf(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
this.canDelete$ = this.activeEPerson$.pipe(
|
||||||
|
switchMap((eperson) => this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined))
|
||||||
|
);
|
||||||
|
this.canReset$ = observableOf(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -355,7 +350,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
* Emit the updated/created eperson using the EventEmitter submitForm
|
* Emit the updated/created eperson using the EventEmitter submitForm
|
||||||
*/
|
*/
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
this.epersonService.getActiveEPerson().pipe(take(1)).subscribe(
|
this.activeEPerson$.pipe(take(1)).subscribe(
|
||||||
(ePerson: EPerson) => {
|
(ePerson: EPerson) => {
|
||||||
const values = {
|
const values = {
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -474,7 +469,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
* It'll either show a success or error message depending on whether the delete was successful or not.
|
* It'll either show a success or error message depending on whether the delete was successful or not.
|
||||||
*/
|
*/
|
||||||
delete(): void {
|
delete(): void {
|
||||||
this.epersonService.getActiveEPerson().pipe(
|
this.activeEPerson$.pipe(
|
||||||
take(1),
|
take(1),
|
||||||
switchMap((eperson: EPerson) => {
|
switchMap((eperson: EPerson) => {
|
||||||
const modalRef = this.modalService.open(ConfirmationModalComponent);
|
const modalRef = this.modalService.open(ConfirmationModalComponent);
|
||||||
@@ -578,7 +573,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
* Update the list of groups by fetching it from the rest api or cache
|
* Update the list of groups by fetching it from the rest api or cache
|
||||||
*/
|
*/
|
||||||
private updateGroups(options) {
|
private updateGroups(options) {
|
||||||
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
|
this.subs.push(this.activeEPerson$.subscribe((eperson: EPerson) => {
|
||||||
this.groups = this.groupsDataService.findListByHref(eperson._links.groups.href, options);
|
this.groups = this.groupsDataService.findListByHref(eperson._links.groups.href, options);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<div class="group-form row">
|
<div class="group-form row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
|
||||||
<div *ngIf="groupDataService.getActiveGroup() | async; then editHeader; else createHeader"></div>
|
<div *ngIf="activeGroup$ | async; then editHeader; else createHeader"></div>
|
||||||
|
|
||||||
<ng-template #createHeader>
|
<ng-template #createHeader>
|
||||||
<h1 class="border-bottom pb-2">{{messagePrefix + '.head.create' | translate}}</h1>
|
<h1 class="border-bottom pb-2">{{messagePrefix + '.head.create' | translate}}</h1>
|
||||||
@@ -23,11 +23,15 @@
|
|||||||
</h1>
|
</h1>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ds-alert *ngIf="groupBeingEdited?.permanent" [type]="AlertTypeEnum.Warning"
|
<ng-container *ngIf="(activeGroup$ | async) as groupBeingEdited">
|
||||||
[content]="messagePrefix + '.alert.permanent'"></ds-alert>
|
<ds-alert *ngIf="groupBeingEdited?.permanent" [type]="AlertType.Warning"
|
||||||
<ds-alert *ngIf="!(canEdit$ | async) && (groupDataService.getActiveGroup() | async)" [type]="AlertTypeEnum.Warning"
|
[content]="messagePrefix + '.alert.permanent'"></ds-alert>
|
||||||
[content]="(messagePrefix + '.alert.workflowGroup' | translate:{ name: dsoNameService.getName((getLinkedDSO(groupBeingEdited) | async)?.payload), comcol: (getLinkedDSO(groupBeingEdited) | async)?.payload?.type, comcolEditRolesRoute: (getLinkedEditRolesRoute(groupBeingEdited) | async) })">
|
<ng-container *ngIf="(activeGroupLinkedDSO$ | async) as activeGroupLinkedDSO">
|
||||||
</ds-alert>
|
<ds-alert *ngIf="!(canEdit$ | async)" [type]="AlertType.Warning"
|
||||||
|
[content]="(messagePrefix + '.alert.workflowGroup' | translate:{ name: dsoNameService.getName(activeGroupLinkedDSO), comcol: activeGroupLinkedDSO.type, comcolEditRolesRoute: (linkedEditRolesRoute$ | async) })">
|
||||||
|
</ds-alert>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<ds-form [formId]="formId"
|
<ds-form [formId]="formId"
|
||||||
[formModel]="formModel"
|
[formModel]="formModel"
|
||||||
@@ -39,22 +43,21 @@
|
|||||||
<button (click)="onCancel()" type="button"
|
<button (click)="onCancel()" type="button"
|
||||||
class="btn btn-outline-secondary"><i class="fas fa-arrow-left"></i> {{messagePrefix + '.return' | translate}}</button>
|
class="btn btn-outline-secondary"><i class="fas fa-arrow-left"></i> {{messagePrefix + '.return' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
<div after *ngIf="(canEdit$ | async) && !groupBeingEdited?.permanent" class="btn-group">
|
<div after *ngIf="(canEdit$ | async) && !(activeGroup$ | async)?.permanent" class="btn-group">
|
||||||
<button (click)="delete()" class="btn btn-danger delete-button" type="button">
|
<button (click)="delete()" class="btn btn-danger delete-button" type="button">
|
||||||
<i class="fa fa-trash"></i> {{ messagePrefix + '.actions.delete' | translate}}
|
<i class="fa fa-trash"></i> {{ messagePrefix + '.actions.delete' | translate}}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</ds-form>
|
</ds-form>
|
||||||
|
|
||||||
<div class="mb-5">
|
<ng-container *ngIf="(activeGroup$ | async) as groupBeingEdited">
|
||||||
<ds-members-list *ngIf="groupBeingEdited != null"
|
<div class="mb-5">
|
||||||
[messagePrefix]="messagePrefix + '.members-list'"></ds-members-list>
|
<ds-members-list *ngIf="groupBeingEdited != null"
|
||||||
</div>
|
[messagePrefix]="messagePrefix + '.members-list'"></ds-members-list>
|
||||||
<ds-subgroups-list *ngIf="groupBeingEdited != null"
|
</div>
|
||||||
[messagePrefix]="messagePrefix + '.subgroups-list'"></ds-subgroups-list>
|
<ds-subgroups-list *ngIf="groupBeingEdited != null"
|
||||||
|
[messagePrefix]="messagePrefix + '.subgroups-list'"></ds-subgroups-list>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { UntypedFormControl, UntypedFormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
import { UntypedFormControl, UntypedFormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
import { BrowserModule, By } from '@angular/platform-browser';
|
import { BrowserModule, By } from '@angular/platform-browser';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { Observable, of as observableOf } from 'rxjs';
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service';
|
||||||
import { ObjectCacheService } from '../../../core/cache/object-cache.service';
|
import { ObjectCacheService } from '../../../core/cache/object-cache.service';
|
||||||
@@ -30,8 +30,6 @@ import { GroupMock, GroupMock2 } from '../../../shared/testing/group-mock';
|
|||||||
import { GroupFormComponent } from './group-form.component';
|
import { GroupFormComponent } from './group-form.component';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
import { getMockFormBuilderService } from '../../../shared/mocks/form-builder-service.mock';
|
import { getMockFormBuilderService } from '../../../shared/mocks/form-builder-service.mock';
|
||||||
import { getMockTranslateService } from '../../../shared/mocks/translate.service.mock';
|
|
||||||
import { TranslateLoaderMock } from '../../../shared/testing/translate-loader.mock';
|
|
||||||
import { RouterMock } from '../../../shared/mocks/router.mock';
|
import { RouterMock } from '../../../shared/mocks/router.mock';
|
||||||
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
|
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
|
||||||
import { Operation } from 'fast-json-patch';
|
import { Operation } from 'fast-json-patch';
|
||||||
@@ -39,23 +37,24 @@ import { ValidateGroupExists } from './validators/group-exists.validator';
|
|||||||
import { NoContent } from '../../../core/shared/NoContent.model';
|
import { NoContent } from '../../../core/shared/NoContent.model';
|
||||||
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
|
||||||
import { DSONameServiceMock } from '../../../shared/mocks/dso-name.service.mock';
|
import { DSONameServiceMock } from '../../../shared/mocks/dso-name.service.mock';
|
||||||
|
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
||||||
|
|
||||||
describe('GroupFormComponent', () => {
|
describe('GroupFormComponent', () => {
|
||||||
let component: GroupFormComponent;
|
let component: GroupFormComponent;
|
||||||
let fixture: ComponentFixture<GroupFormComponent>;
|
let fixture: ComponentFixture<GroupFormComponent>;
|
||||||
let translateService: TranslateService;
|
|
||||||
let builderService: FormBuilderService;
|
let builderService: FormBuilderService;
|
||||||
let ePersonDataServiceStub: any;
|
let ePersonDataServiceStub: any;
|
||||||
let groupsDataServiceStub: any;
|
let groupsDataServiceStub: any;
|
||||||
let dsoDataServiceStub: any;
|
let dsoDataServiceStub: any;
|
||||||
let authorizationService: AuthorizationDataService;
|
let authorizationService: AuthorizationDataService;
|
||||||
let notificationService: NotificationsServiceStub;
|
let notificationService: NotificationsServiceStub;
|
||||||
let router;
|
let router: RouterMock;
|
||||||
|
let route: ActivatedRouteStub;
|
||||||
|
|
||||||
let groups;
|
let groups: Group[];
|
||||||
let groupName;
|
let groupName: string;
|
||||||
let groupDescription;
|
let groupDescription: string;
|
||||||
let expected;
|
let expected: Group;
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
groups = [GroupMock, GroupMock2];
|
groups = [GroupMock, GroupMock2];
|
||||||
@@ -70,6 +69,15 @@ describe('GroupFormComponent', () => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
object: createSuccessfulRemoteDataObject$(undefined),
|
||||||
|
_links: {
|
||||||
|
self: {
|
||||||
|
href: 'group-selflink',
|
||||||
|
},
|
||||||
|
object: {
|
||||||
|
href: 'group-objectlink',
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
ePersonDataServiceStub = {};
|
ePersonDataServiceStub = {};
|
||||||
groupsDataServiceStub = {
|
groupsDataServiceStub = {
|
||||||
@@ -106,7 +114,14 @@ describe('GroupFormComponent', () => {
|
|||||||
create(group: Group): Observable<RemoteData<Group>> {
|
create(group: Group): Observable<RemoteData<Group>> {
|
||||||
this.allGroups = [...this.allGroups, group];
|
this.allGroups = [...this.allGroups, group];
|
||||||
this.createdGroup = Object.assign({}, group, {
|
this.createdGroup = Object.assign({}, group, {
|
||||||
_links: { self: { href: 'group-selflink' } }
|
_links: {
|
||||||
|
self: {
|
||||||
|
href: 'group-selflink',
|
||||||
|
},
|
||||||
|
object: {
|
||||||
|
href: 'group-objectlink',
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
return createSuccessfulRemoteDataObject$(this.createdGroup);
|
return createSuccessfulRemoteDataObject$(this.createdGroup);
|
||||||
},
|
},
|
||||||
@@ -188,17 +203,13 @@ describe('GroupFormComponent', () => {
|
|||||||
return typeof value === 'object' && value !== null;
|
return typeof value === 'object' && value !== null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
translateService = getMockTranslateService();
|
|
||||||
router = new RouterMock();
|
router = new RouterMock();
|
||||||
|
route = new ActivatedRouteStub();
|
||||||
notificationService = new NotificationsServiceStub();
|
notificationService = new NotificationsServiceStub();
|
||||||
|
|
||||||
return TestBed.configureTestingModule({
|
return TestBed.configureTestingModule({
|
||||||
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
|
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
|
||||||
TranslateModule.forRoot({
|
TranslateModule.forRoot(),
|
||||||
loader: {
|
|
||||||
provide: TranslateLoader,
|
|
||||||
useClass: TranslateLoaderMock
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
declarations: [GroupFormComponent],
|
declarations: [GroupFormComponent],
|
||||||
providers: [
|
providers: [
|
||||||
@@ -216,14 +227,11 @@ describe('GroupFormComponent', () => {
|
|||||||
{ provide: Store, useValue: {} },
|
{ provide: Store, useValue: {} },
|
||||||
{ provide: RemoteDataBuildService, useValue: {} },
|
{ provide: RemoteDataBuildService, useValue: {} },
|
||||||
{ provide: HALEndpointService, useValue: {} },
|
{ provide: HALEndpointService, useValue: {} },
|
||||||
{
|
{ provide: ActivatedRoute, useValue: route },
|
||||||
provide: ActivatedRoute,
|
|
||||||
useValue: { data: observableOf({ dso: { payload: {} } }), params: observableOf({}) }
|
|
||||||
},
|
|
||||||
{ provide: Router, useValue: router },
|
{ provide: Router, useValue: router },
|
||||||
{ provide: AuthorizationDataService, useValue: authorizationService },
|
{ provide: AuthorizationDataService, useValue: authorizationService },
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -236,8 +244,8 @@ describe('GroupFormComponent', () => {
|
|||||||
describe('when submitting the form', () => {
|
describe('when submitting the form', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(component.submitForm, 'emit');
|
spyOn(component.submitForm, 'emit');
|
||||||
component.groupName.value = groupName;
|
component.groupName.setValue(groupName);
|
||||||
component.groupDescription.value = groupDescription;
|
component.groupDescription.setValue(groupDescription);
|
||||||
});
|
});
|
||||||
describe('without active Group', () => {
|
describe('without active Group', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -245,14 +253,22 @@ describe('GroupFormComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should emit a new group using the correct values', (async () => {
|
it('should emit a new group using the correct values', (() => {
|
||||||
await fixture.whenStable().then(() => {
|
expect(component.submitForm.emit).toHaveBeenCalledWith(jasmine.objectContaining({
|
||||||
expect(component.submitForm.emit).toHaveBeenCalledWith(expected);
|
name: groupName,
|
||||||
});
|
metadata: {
|
||||||
|
'dc.description': [
|
||||||
|
{
|
||||||
|
value: groupDescription,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}));
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with active Group', () => {
|
describe('with active Group', () => {
|
||||||
let expected2;
|
let expected2: Group;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
expected2 = Object.assign(new Group(), {
|
expected2 = Object.assign(new Group(), {
|
||||||
name: 'newGroupName',
|
name: 'newGroupName',
|
||||||
@@ -263,15 +279,24 @@ describe('GroupFormComponent', () => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
object: createSuccessfulRemoteDataObject$(undefined),
|
||||||
|
_links: {
|
||||||
|
self: {
|
||||||
|
href: 'group-selflink',
|
||||||
|
},
|
||||||
|
object: {
|
||||||
|
href: 'group-objectlink',
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
spyOn(groupsDataServiceStub, 'getActiveGroup').and.returnValue(observableOf(expected));
|
spyOn(groupsDataServiceStub, 'getActiveGroup').and.returnValue(observableOf(expected));
|
||||||
spyOn(groupsDataServiceStub, 'patch').and.returnValue(createSuccessfulRemoteDataObject$(expected2));
|
spyOn(groupsDataServiceStub, 'patch').and.returnValue(createSuccessfulRemoteDataObject$(expected2));
|
||||||
component.groupName.value = 'newGroupName';
|
component.ngOnInit();
|
||||||
component.onSubmit();
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should edit with name and description operations', () => {
|
it('should edit with name and description operations', () => {
|
||||||
|
component.groupName.setValue('newGroupName');
|
||||||
|
component.onSubmit();
|
||||||
const operations = [{
|
const operations = [{
|
||||||
op: 'add',
|
op: 'add',
|
||||||
path: '/metadata/dc.description',
|
path: '/metadata/dc.description',
|
||||||
@@ -285,9 +310,8 @@ describe('GroupFormComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should edit with description operations', () => {
|
it('should edit with description operations', () => {
|
||||||
component.groupName.value = null;
|
component.groupName.setValue(null);
|
||||||
component.onSubmit();
|
component.onSubmit();
|
||||||
fixture.detectChanges();
|
|
||||||
const operations = [{
|
const operations = [{
|
||||||
op: 'add',
|
op: 'add',
|
||||||
path: '/metadata/dc.description',
|
path: '/metadata/dc.description',
|
||||||
@@ -297,9 +321,9 @@ describe('GroupFormComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should edit with name operations', () => {
|
it('should edit with name operations', () => {
|
||||||
component.groupDescription.value = null;
|
component.groupName.setValue('newGroupName');
|
||||||
|
component.groupDescription.setValue(null);
|
||||||
component.onSubmit();
|
component.onSubmit();
|
||||||
fixture.detectChanges();
|
|
||||||
const operations = [{
|
const operations = [{
|
||||||
op: 'replace',
|
op: 'replace',
|
||||||
path: '/name',
|
path: '/name',
|
||||||
@@ -308,12 +332,13 @@ describe('GroupFormComponent', () => {
|
|||||||
expect(groupsDataServiceStub.patch).toHaveBeenCalledWith(expected, operations);
|
expect(groupsDataServiceStub.patch).toHaveBeenCalledWith(expected, operations);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should emit the existing group using the correct new values', (async () => {
|
it('should emit the existing group using the correct new values', () => {
|
||||||
await fixture.whenStable().then(() => {
|
component.onSubmit();
|
||||||
expect(component.submitForm.emit).toHaveBeenCalledWith(expected2);
|
expect(component.submitForm.emit).toHaveBeenCalledWith(expected2);
|
||||||
});
|
});
|
||||||
}));
|
|
||||||
it('should emit success notification', () => {
|
it('should emit success notification', () => {
|
||||||
|
component.onSubmit();
|
||||||
expect(notificationService.success).toHaveBeenCalled();
|
expect(notificationService.success).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -328,11 +353,8 @@ describe('GroupFormComponent', () => {
|
|||||||
|
|
||||||
|
|
||||||
describe('check form validation', () => {
|
describe('check form validation', () => {
|
||||||
let groupCommunity;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
groupName = 'testName';
|
groupName = 'testName';
|
||||||
groupCommunity = 'testgroupCommunity';
|
|
||||||
groupDescription = 'testgroupDescription';
|
groupDescription = 'testgroupDescription';
|
||||||
|
|
||||||
expected = Object.assign(new Group(), {
|
expected = Object.assign(new Group(), {
|
||||||
@@ -344,8 +366,17 @@ describe('GroupFormComponent', () => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
_links: {
|
||||||
|
self: {
|
||||||
|
href: 'group-selflink',
|
||||||
|
},
|
||||||
|
object: {
|
||||||
|
href: 'group-objectlink',
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
spyOn(component.submitForm, 'emit');
|
spyOn(component.submitForm, 'emit');
|
||||||
|
spyOn(dsoDataServiceStub, 'findByHref').and.returnValue(observableOf(expected));
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
component.initialisePage();
|
component.initialisePage();
|
||||||
@@ -395,21 +426,20 @@ describe('GroupFormComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('delete', () => {
|
describe('delete', () => {
|
||||||
let deleteButton;
|
let deleteButton: HTMLButtonElement;
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
component.initialisePage();
|
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
spyOn(groupsDataServiceStub, 'delete').and.callThrough();
|
||||||
|
component.activeGroup$ = observableOf({
|
||||||
|
id: 'active-group',
|
||||||
|
permanent: false,
|
||||||
|
} as Group);
|
||||||
component.canEdit$ = observableOf(true);
|
component.canEdit$ = observableOf(true);
|
||||||
component.groupBeingEdited = {
|
|
||||||
permanent: false
|
component.initialisePage();
|
||||||
} as Group;
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
deleteButton = fixture.debugElement.query(By.css('.delete-button')).nativeElement;
|
deleteButton = fixture.debugElement.query(By.css('.delete-button')).nativeElement;
|
||||||
|
|
||||||
spyOn(groupsDataServiceStub, 'delete').and.callThrough();
|
|
||||||
spyOn(groupsDataServiceStub, 'getActiveGroup').and.returnValue(observableOf({ id: 'active-group' }));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('if confirmed via modal', () => {
|
describe('if confirmed via modal', () => {
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Component, EventEmitter, HostListener, OnDestroy, OnInit, Output, ChangeDetectorRef } from '@angular/core';
|
import { Component, EventEmitter, HostListener, OnDestroy, OnInit, Output, ChangeDetectorRef } from '@angular/core';
|
||||||
import { UntypedFormGroup } from '@angular/forms';
|
import { UntypedFormGroup, AbstractControl } from '@angular/forms';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import {
|
import {
|
||||||
@@ -12,10 +12,9 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
import {
|
import {
|
||||||
combineLatest as observableCombineLatest,
|
combineLatest as observableCombineLatest,
|
||||||
Observable,
|
Observable,
|
||||||
of as observableOf,
|
|
||||||
Subscription,
|
Subscription,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { catchError, map, switchMap, take, filter, debounceTime } from 'rxjs/operators';
|
import { map, switchMap, take, debounceTime } from 'rxjs/operators';
|
||||||
import { getCollectionEditRolesRoute } from '../../../collection-page/collection-page-routing-paths';
|
import { getCollectionEditRolesRoute } from '../../../collection-page/collection-page-routing-paths';
|
||||||
import { getCommunityEditRolesRoute } from '../../../community-page/community-page-routing-paths';
|
import { getCommunityEditRolesRoute } from '../../../community-page/community-page-routing-paths';
|
||||||
import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service';
|
||||||
@@ -24,17 +23,16 @@ import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
|||||||
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { RequestService } from '../../../core/data/request.service';
|
import { RequestService } from '../../../core/data/request.service';
|
||||||
import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
|
|
||||||
import { GroupDataService } from '../../../core/eperson/group-data.service';
|
import { GroupDataService } from '../../../core/eperson/group-data.service';
|
||||||
import { Group } from '../../../core/eperson/models/group.model';
|
import { Group } from '../../../core/eperson/models/group.model';
|
||||||
import { Collection } from '../../../core/shared/collection.model';
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
import { Community } from '../../../core/shared/community.model';
|
import { Community } from '../../../core/shared/community.model';
|
||||||
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
import {
|
import {
|
||||||
|
getAllCompletedRemoteData,
|
||||||
getRemoteDataPayload,
|
getRemoteDataPayload,
|
||||||
getFirstSucceededRemoteData,
|
getFirstSucceededRemoteData,
|
||||||
getFirstCompletedRemoteData,
|
getFirstCompletedRemoteData,
|
||||||
getFirstSucceededRemoteDataPayload
|
|
||||||
} from '../../../core/shared/operators';
|
} from '../../../core/shared/operators';
|
||||||
import { AlertType } from '../../../shared/alert/alert-type';
|
import { AlertType } from '../../../shared/alert/alert-type';
|
||||||
import { ConfirmationModalComponent } from '../../../shared/confirmation-modal/confirmation-modal.component';
|
import { ConfirmationModalComponent } from '../../../shared/confirmation-modal/confirmation-modal.component';
|
||||||
@@ -68,9 +66,9 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
|||||||
/**
|
/**
|
||||||
* Dynamic models for the inputs of form
|
* Dynamic models for the inputs of form
|
||||||
*/
|
*/
|
||||||
groupName: DynamicInputModel;
|
groupName: AbstractControl;
|
||||||
groupCommunity: DynamicInputModel;
|
groupCommunity: AbstractControl;
|
||||||
groupDescription: DynamicTextAreaModel;
|
groupDescription: AbstractControl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of all dynamic input models
|
* A list of all dynamic input models
|
||||||
@@ -113,21 +111,30 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
subs: Subscription[] = [];
|
subs: Subscription[] = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* Group currently being edited
|
|
||||||
*/
|
|
||||||
groupBeingEdited: Group;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observable whether or not the logged in user is allowed to delete the Group & doesn't have a linked object (community / collection linked to workspace group
|
* Observable whether or not the logged in user is allowed to delete the Group & doesn't have a linked object (community / collection linked to workspace group
|
||||||
*/
|
*/
|
||||||
canEdit$: Observable<boolean>;
|
canEdit$: Observable<boolean>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The AlertType enumeration
|
* The current {@link Group}
|
||||||
* @type {AlertType}
|
|
||||||
*/
|
*/
|
||||||
public AlertTypeEnum = AlertType;
|
activeGroup$: Observable<Group>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current {@link Group}'s linked {@link Community}/{@link Collection}
|
||||||
|
*/
|
||||||
|
activeGroupLinkedDSO$: Observable<DSpaceObject>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Link to the current {@link Group}'s {@link Community}/{@link Collection} edit role tab
|
||||||
|
*/
|
||||||
|
linkedEditRolesRoute$: Observable<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The AlertType enumeration
|
||||||
|
*/
|
||||||
|
public readonly AlertType = AlertType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscription to email field value change
|
* Subscription to email field value change
|
||||||
@@ -137,126 +144,121 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public groupDataService: GroupDataService,
|
public groupDataService: GroupDataService,
|
||||||
private ePersonDataService: EPersonDataService,
|
protected dSpaceObjectDataService: DSpaceObjectDataService,
|
||||||
private dSpaceObjectDataService: DSpaceObjectDataService,
|
protected formBuilderService: FormBuilderService,
|
||||||
private formBuilderService: FormBuilderService,
|
protected translateService: TranslateService,
|
||||||
private translateService: TranslateService,
|
protected notificationsService: NotificationsService,
|
||||||
private notificationsService: NotificationsService,
|
protected route: ActivatedRoute,
|
||||||
private route: ActivatedRoute,
|
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
private authorizationService: AuthorizationDataService,
|
protected authorizationService: AuthorizationDataService,
|
||||||
private modalService: NgbModal,
|
protected modalService: NgbModal,
|
||||||
public requestService: RequestService,
|
public requestService: RequestService,
|
||||||
protected changeDetectorRef: ChangeDetectorRef,
|
protected changeDetectorRef: ChangeDetectorRef,
|
||||||
public dsoNameService: DSONameService,
|
public dsoNameService: DSONameService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit(): void {
|
||||||
|
if (this.route.snapshot.params.groupId !== 'newGroup') {
|
||||||
|
this.setActiveGroup(this.route.snapshot.params.groupId);
|
||||||
|
}
|
||||||
|
this.activeGroup$ = this.groupDataService.getActiveGroup();
|
||||||
|
this.activeGroupLinkedDSO$ = this.getActiveGroupLinkedDSO();
|
||||||
|
this.linkedEditRolesRoute$ = this.getLinkedEditRolesRoute();
|
||||||
|
this.canEdit$ = this.activeGroupLinkedDSO$.pipe(
|
||||||
|
switchMap((dso: DSpaceObject) => {
|
||||||
|
if (hasValue(dso)) {
|
||||||
|
return [false];
|
||||||
|
} else {
|
||||||
|
return this.activeGroup$.pipe(
|
||||||
|
hasValueOperator(),
|
||||||
|
switchMap((group: Group) => this.authorizationService.isAuthorized(FeatureID.CanDelete, group.self)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
this.initialisePage();
|
this.initialisePage();
|
||||||
}
|
}
|
||||||
|
|
||||||
initialisePage() {
|
initialisePage() {
|
||||||
this.subs.push(this.route.params.subscribe((params) => {
|
const groupNameModel = new DynamicInputModel({
|
||||||
if (params.groupId !== 'newGroup') {
|
id: 'groupName',
|
||||||
this.setActiveGroup(params.groupId);
|
label: this.translateService.instant(`${this.messagePrefix}.groupName`),
|
||||||
}
|
name: 'groupName',
|
||||||
}));
|
validators: {
|
||||||
this.canEdit$ = this.groupDataService.getActiveGroup().pipe(
|
required: null,
|
||||||
hasValueOperator(),
|
},
|
||||||
switchMap((group: Group) => {
|
required: true,
|
||||||
return observableCombineLatest([
|
|
||||||
this.authorizationService.isAuthorized(FeatureID.CanDelete, isNotEmpty(group) ? group.self : undefined),
|
|
||||||
this.hasLinkedDSO(group),
|
|
||||||
]).pipe(
|
|
||||||
map(([isAuthorized, hasLinkedDSO]: [boolean, boolean]) => isAuthorized && !hasLinkedDSO),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
observableCombineLatest([
|
|
||||||
this.translateService.get(`${this.messagePrefix}.groupName`),
|
|
||||||
this.translateService.get(`${this.messagePrefix}.groupCommunity`),
|
|
||||||
this.translateService.get(`${this.messagePrefix}.groupDescription`)
|
|
||||||
]).subscribe(([groupName, groupCommunity, groupDescription]) => {
|
|
||||||
this.groupName = new DynamicInputModel({
|
|
||||||
id: 'groupName',
|
|
||||||
label: groupName,
|
|
||||||
name: 'groupName',
|
|
||||||
validators: {
|
|
||||||
required: null,
|
|
||||||
},
|
|
||||||
required: true,
|
|
||||||
});
|
|
||||||
this.groupCommunity = new DynamicInputModel({
|
|
||||||
id: 'groupCommunity',
|
|
||||||
label: groupCommunity,
|
|
||||||
name: 'groupCommunity',
|
|
||||||
required: false,
|
|
||||||
readOnly: true,
|
|
||||||
});
|
|
||||||
this.groupDescription = new DynamicTextAreaModel({
|
|
||||||
id: 'groupDescription',
|
|
||||||
label: groupDescription,
|
|
||||||
name: 'groupDescription',
|
|
||||||
required: false,
|
|
||||||
spellCheck: environment.form.spellCheck,
|
|
||||||
});
|
|
||||||
this.formModel = [
|
|
||||||
this.groupName,
|
|
||||||
this.groupDescription,
|
|
||||||
];
|
|
||||||
this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
|
|
||||||
|
|
||||||
if (!!this.formGroup.controls.groupName) {
|
|
||||||
this.formGroup.controls.groupName.setAsyncValidators(ValidateGroupExists.createValidator(this.groupDataService));
|
|
||||||
this.groupNameValueChangeSubscribe = this.groupName.valueChanges.pipe(debounceTime(300)).subscribe(() => {
|
|
||||||
this.changeDetectorRef.detectChanges();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.subs.push(
|
|
||||||
observableCombineLatest([
|
|
||||||
this.groupDataService.getActiveGroup(),
|
|
||||||
this.canEdit$,
|
|
||||||
this.groupDataService.getActiveGroup()
|
|
||||||
.pipe(filter((activeGroup) => hasValue(activeGroup)),switchMap((activeGroup) => this.getLinkedDSO(activeGroup).pipe(getFirstSucceededRemoteDataPayload())))
|
|
||||||
]).subscribe(([activeGroup, canEdit, linkedObject]) => {
|
|
||||||
|
|
||||||
if (activeGroup != null) {
|
|
||||||
|
|
||||||
// Disable group name exists validator
|
|
||||||
this.formGroup.controls.groupName.clearAsyncValidators();
|
|
||||||
|
|
||||||
this.groupBeingEdited = activeGroup;
|
|
||||||
|
|
||||||
if (linkedObject?.name) {
|
|
||||||
if (!this.formGroup.controls.groupCommunity) {
|
|
||||||
this.formBuilderService.insertFormGroupControl(1, this.formGroup, this.formModel, this.groupCommunity);
|
|
||||||
this.formGroup.patchValue({
|
|
||||||
groupName: activeGroup.name,
|
|
||||||
groupCommunity: linkedObject?.name ?? '',
|
|
||||||
groupDescription: activeGroup.firstMetadataValue('dc.description'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.formModel = [
|
|
||||||
this.groupName,
|
|
||||||
this.groupDescription,
|
|
||||||
];
|
|
||||||
this.formGroup.patchValue({
|
|
||||||
groupName: activeGroup.name,
|
|
||||||
groupDescription: activeGroup.firstMetadataValue('dc.description'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!canEdit || activeGroup.permanent) {
|
|
||||||
this.formGroup.disable();
|
|
||||||
}
|
|
||||||
}, 200);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
const groupCommunityModel = new DynamicInputModel({
|
||||||
|
id: 'groupCommunity',
|
||||||
|
label: this.translateService.instant(`${this.messagePrefix}.groupCommunity`),
|
||||||
|
name: 'groupCommunity',
|
||||||
|
required: false,
|
||||||
|
readOnly: true,
|
||||||
|
});
|
||||||
|
const groupDescriptionModel = new DynamicTextAreaModel({
|
||||||
|
id: 'groupDescription',
|
||||||
|
label: this.translateService.instant(`${this.messagePrefix}.groupDescription`),
|
||||||
|
name: 'groupDescription',
|
||||||
|
required: false,
|
||||||
|
spellCheck: environment.form.spellCheck,
|
||||||
|
});
|
||||||
|
this.formModel = [
|
||||||
|
groupNameModel,
|
||||||
|
groupDescriptionModel,
|
||||||
|
];
|
||||||
|
this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
|
||||||
|
this.groupName = this.formGroup.get('groupName');
|
||||||
|
this.groupDescription = this.formGroup.get('groupDescription');
|
||||||
|
|
||||||
|
if (hasValue(this.groupName)) {
|
||||||
|
this.groupName.setAsyncValidators(ValidateGroupExists.createValidator(this.groupDataService));
|
||||||
|
this.groupNameValueChangeSubscribe = this.groupName.valueChanges.pipe(debounceTime(300)).subscribe(() => {
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.subs.push(
|
||||||
|
observableCombineLatest([
|
||||||
|
this.activeGroup$,
|
||||||
|
this.canEdit$,
|
||||||
|
this.activeGroupLinkedDSO$,
|
||||||
|
]).subscribe(([activeGroup, canEdit, linkedObject]) => {
|
||||||
|
|
||||||
|
if (activeGroup != null) {
|
||||||
|
|
||||||
|
// Disable group name exists validator
|
||||||
|
this.formGroup.controls.groupName.clearAsyncValidators();
|
||||||
|
|
||||||
|
if (isNotEmpty(linkedObject?.name)) {
|
||||||
|
if (!this.formGroup.controls.groupCommunity) {
|
||||||
|
this.formBuilderService.insertFormGroupControl(1, this.formGroup, this.formModel, groupCommunityModel);
|
||||||
|
this.groupDescription = this.formGroup.get('groupCommunity');
|
||||||
|
}
|
||||||
|
this.formGroup.patchValue({
|
||||||
|
groupName: activeGroup.name,
|
||||||
|
groupCommunity: linkedObject?.name ?? '',
|
||||||
|
groupDescription: activeGroup.firstMetadataValue('dc.description'),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.formModel = [
|
||||||
|
groupNameModel,
|
||||||
|
groupDescriptionModel,
|
||||||
|
];
|
||||||
|
this.formGroup.patchValue({
|
||||||
|
groupName: activeGroup.name,
|
||||||
|
groupDescription: activeGroup.firstMetadataValue('dc.description'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!canEdit || activeGroup.permanent) {
|
||||||
|
this.formGroup.disable();
|
||||||
|
} else {
|
||||||
|
this.formGroup.enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -275,25 +277,22 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
|||||||
* Emit the updated/created eperson using the EventEmitter submitForm
|
* Emit the updated/created eperson using the EventEmitter submitForm
|
||||||
*/
|
*/
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe(
|
this.activeGroup$.pipe(take(1)).subscribe((group: Group) => {
|
||||||
(group: Group) => {
|
if (group === null) {
|
||||||
const values = {
|
this.createNewGroup({
|
||||||
name: this.groupName.value,
|
name: this.groupName.value,
|
||||||
metadata: {
|
metadata: {
|
||||||
'dc.description': [
|
'dc.description': [
|
||||||
{
|
{
|
||||||
value: this.groupDescription.value
|
value: this.groupDescription.value,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
if (group === null) {
|
} else {
|
||||||
this.createNewGroup(values);
|
this.editGroup(group);
|
||||||
} else {
|
|
||||||
this.editGroup(group);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -399,7 +398,7 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
|||||||
* @param groupSelfLink SelfLink of group to set as active
|
* @param groupSelfLink SelfLink of group to set as active
|
||||||
*/
|
*/
|
||||||
setActiveGroupWithLink(groupSelfLink: string) {
|
setActiveGroupWithLink(groupSelfLink: string) {
|
||||||
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => {
|
this.activeGroup$.pipe(take(1)).subscribe((activeGroup: Group) => {
|
||||||
if (activeGroup === null) {
|
if (activeGroup === null) {
|
||||||
this.groupDataService.cancelEditGroup();
|
this.groupDataService.cancelEditGroup();
|
||||||
this.groupDataService.findByHref(groupSelfLink, false, false, followLink('subgroups'), followLink('epersons'), followLink('object'))
|
this.groupDataService.findByHref(groupSelfLink, false, false, followLink('subgroups'), followLink('epersons'), followLink('object'))
|
||||||
@@ -418,7 +417,7 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
|||||||
* It'll either show a success or error message depending on whether the delete was successful or not.
|
* It'll either show a success or error message depending on whether the delete was successful or not.
|
||||||
*/
|
*/
|
||||||
delete() {
|
delete() {
|
||||||
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((group: Group) => {
|
this.activeGroup$.pipe(take(1)).subscribe((group: Group) => {
|
||||||
const modalRef = this.modalService.open(ConfirmationModalComponent);
|
const modalRef = this.modalService.open(ConfirmationModalComponent);
|
||||||
modalRef.componentInstance.dso = group;
|
modalRef.componentInstance.dso = group;
|
||||||
modalRef.componentInstance.headerLabel = this.messagePrefix + '.delete-group.modal.header';
|
modalRef.componentInstance.headerLabel = this.messagePrefix + '.delete-group.modal.header';
|
||||||
@@ -462,52 +461,38 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if group has a linked object (community or collection linked to a workflow group)
|
* Get the active {@link Group}'s linked object if it has one ({@link Community} or {@link Collection} linked to a
|
||||||
* @param group
|
* workflow group)
|
||||||
*/
|
*/
|
||||||
hasLinkedDSO(group: Group): Observable<boolean> {
|
getActiveGroupLinkedDSO(): Observable<DSpaceObject> {
|
||||||
if (hasValue(group) && hasValue(group._links.object.href)) {
|
return this.activeGroup$.pipe(
|
||||||
return this.getLinkedDSO(group).pipe(
|
hasValueOperator(),
|
||||||
map((rd: RemoteData<DSpaceObject>) => {
|
switchMap((group: Group) => {
|
||||||
return hasValue(rd) && hasValue(rd.payload);
|
if (group.object === undefined) {
|
||||||
}),
|
return this.dSpaceObjectDataService.findByHref(group._links.object.href);
|
||||||
catchError(() => observableOf(false)),
|
}
|
||||||
);
|
return group.object;
|
||||||
}
|
}),
|
||||||
|
getAllCompletedRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get group's linked object if it has one (community or collection linked to a workflow group)
|
* Get the route to the edit roles tab of the active {@link Group}'s linked object (community or collection linked
|
||||||
* @param group
|
* to a workflow group) if it has one
|
||||||
*/
|
*/
|
||||||
getLinkedDSO(group: Group): Observable<RemoteData<DSpaceObject>> {
|
getLinkedEditRolesRoute(): Observable<string> {
|
||||||
if (hasValue(group) && hasValue(group._links.object.href)) {
|
return this.activeGroupLinkedDSO$.pipe(
|
||||||
if (group.object === undefined) {
|
hasValueOperator(),
|
||||||
return this.dSpaceObjectDataService.findByHref(group._links.object.href);
|
map((dso: DSpaceObject) => {
|
||||||
}
|
switch ((dso as any).type) {
|
||||||
return group.object;
|
case Community.type.value:
|
||||||
}
|
return getCommunityEditRolesRoute(dso.id);
|
||||||
}
|
case Collection.type.value:
|
||||||
|
return getCollectionEditRolesRoute(dso.id);
|
||||||
/**
|
}
|
||||||
* Get the route to the edit roles tab of the group's linked object (community or collection linked to a workflow group) if it has one
|
}),
|
||||||
* @param group
|
);
|
||||||
*/
|
|
||||||
getLinkedEditRolesRoute(group: Group): Observable<string> {
|
|
||||||
if (hasValue(group) && hasValue(group._links.object.href)) {
|
|
||||||
return this.getLinkedDSO(group).pipe(
|
|
||||||
map((rd: RemoteData<DSpaceObject>) => {
|
|
||||||
if (hasValue(rd) && hasValue(rd.payload)) {
|
|
||||||
const dso = rd.payload;
|
|
||||||
switch ((dso as any).type) {
|
|
||||||
case Community.type.value:
|
|
||||||
return getCommunityEditRolesRoute(rd.payload.id);
|
|
||||||
case Collection.type.value:
|
|
||||||
return getCollectionEditRolesRoute(rd.payload.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,9 +9,9 @@
|
|||||||
|
|
||||||
|
|
||||||
<ds-pagination
|
<ds-pagination
|
||||||
*ngIf="(bitstreamFormats | async)?.payload?.totalElements > 0"
|
*ngIf="(bitstreamFormats$ | async)?.payload?.totalElements > 0"
|
||||||
[paginationOptions]="pageConfig"
|
[paginationOptions]="pageConfig"
|
||||||
[collectionSize]="(bitstreamFormats | async)?.payload?.totalElements"
|
[collectionSize]="(bitstreamFormats$ | async)?.payload?.totalElements"
|
||||||
[hideGear]="false"
|
[hideGear]="false"
|
||||||
[hidePagerWhenSinglePage]="true">
|
[hidePagerWhenSinglePage]="true">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
@@ -26,12 +26,12 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let bitstreamFormat of (bitstreamFormats | async)?.payload?.page">
|
<tr *ngFor="let bitstreamFormat of (bitstreamFormats$ | async)?.payload?.page">
|
||||||
<td>
|
<td>
|
||||||
<label class="mb-0">
|
<label class="mb-0">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
[attr.aria-label]="'admin.registries.bitstream-formats.select' | translate"
|
[attr.aria-label]="'admin.registries.bitstream-formats.select' | translate"
|
||||||
[checked]="isSelected(bitstreamFormat) | async"
|
[checked]="(selectedBitstreamFormatIDs$ | async)?.includes(bitstreamFormat.id)"
|
||||||
(change)="selectBitStreamFormat(bitstreamFormat, $event)"
|
(change)="selectBitStreamFormat(bitstreamFormat, $event)"
|
||||||
>
|
>
|
||||||
<span class="sr-only">{{'admin.registries.bitstream-formats.select' | translate}}}</span>
|
<span class="sr-only">{{'admin.registries.bitstream-formats.select' | translate}}}</span>
|
||||||
@@ -46,13 +46,13 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</ds-pagination>
|
</ds-pagination>
|
||||||
<div *ngIf="(bitstreamFormats | async)?.payload?.totalElements == 0" class="alert alert-info" role="alert">
|
<div *ngIf="(bitstreamFormats$ | async)?.payload?.totalElements == 0" class="alert alert-info" role="alert">
|
||||||
{{'admin.registries.bitstream-formats.no-items' | translate}}
|
{{'admin.registries.bitstream-formats.no-items' | translate}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button *ngIf="(bitstreamFormats | async)?.payload?.page?.length > 0" class="btn btn-primary deselect" (click)="deselectAll()">{{'admin.registries.bitstream-formats.table.deselect-all' | translate}}</button>
|
<button *ngIf="(bitstreamFormats$ | async)?.payload?.page?.length > 0" class="btn btn-primary deselect" (click)="deselectAll()">{{'admin.registries.bitstream-formats.table.deselect-all' | translate}}</button>
|
||||||
<button *ngIf="(bitstreamFormats | async)?.payload?.page?.length > 0" type="submit" class="btn btn-danger float-right" (click)="deleteFormats()">{{'admin.registries.bitstream-formats.table.delete' | translate}}</button>
|
<button *ngIf="(bitstreamFormats$ | async)?.payload?.page?.length > 0" type="submit" class="btn btn-danger float-right" (click)="deleteFormats()">{{'admin.registries.bitstream-formats.table.delete' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -16,8 +16,7 @@ import { NotificationsServiceStub } from '../../../shared/testing/notifications-
|
|||||||
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
|
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
|
||||||
import { BitstreamFormatSupportLevel } from '../../../core/shared/bitstream-format-support-level';
|
import { BitstreamFormatSupportLevel } from '../../../core/shared/bitstream-format-support-level';
|
||||||
import { XSRFService } from '../../../core/xsrf/xsrf.service';
|
import { XSRFService } from '../../../core/xsrf/xsrf.service';
|
||||||
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
|
import { hot } from 'jasmine-marbles';
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
|
||||||
import {
|
import {
|
||||||
createNoContentRemoteDataObject$,
|
createNoContentRemoteDataObject$,
|
||||||
createSuccessfulRemoteDataObject,
|
createSuccessfulRemoteDataObject,
|
||||||
@@ -32,7 +31,6 @@ describe('BitstreamFormatsComponent', () => {
|
|||||||
let comp: BitstreamFormatsComponent;
|
let comp: BitstreamFormatsComponent;
|
||||||
let fixture: ComponentFixture<BitstreamFormatsComponent>;
|
let fixture: ComponentFixture<BitstreamFormatsComponent>;
|
||||||
let bitstreamFormatService;
|
let bitstreamFormatService;
|
||||||
let scheduler: TestScheduler;
|
|
||||||
let notificationsServiceStub;
|
let notificationsServiceStub;
|
||||||
let paginationService;
|
let paginationService;
|
||||||
|
|
||||||
@@ -87,8 +85,6 @@ describe('BitstreamFormatsComponent', () => {
|
|||||||
const initAsync = () => {
|
const initAsync = () => {
|
||||||
notificationsServiceStub = new NotificationsServiceStub();
|
notificationsServiceStub = new NotificationsServiceStub();
|
||||||
|
|
||||||
scheduler = getTestScheduler();
|
|
||||||
|
|
||||||
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
|
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
|
||||||
findAll: observableOf(mockFormatsRD),
|
findAll: observableOf(mockFormatsRD),
|
||||||
find: createSuccessfulRemoteDataObject$(mockFormatsList[0]),
|
find: createSuccessfulRemoteDataObject$(mockFormatsList[0]),
|
||||||
@@ -180,17 +176,17 @@ describe('BitstreamFormatsComponent', () => {
|
|||||||
beforeEach(waitForAsync(initAsync));
|
beforeEach(waitForAsync(initAsync));
|
||||||
beforeEach(initBeforeEach);
|
beforeEach(initBeforeEach);
|
||||||
it('should return an observable of true if the provided bistream is in the list returned by the service', () => {
|
it('should return an observable of true if the provided bistream is in the list returned by the service', () => {
|
||||||
const result = comp.isSelected(bitstreamFormat1);
|
comp.selectedBitstreamFormatIDs().subscribe((selectedBitstreamFormatIDs: string[]) => {
|
||||||
|
expect(selectedBitstreamFormatIDs).toContain(bitstreamFormat1.id);
|
||||||
expect(result).toBeObservable(cold('b', { b: true }));
|
});
|
||||||
});
|
});
|
||||||
it('should return an observable of false if the provided bistream is not in the list returned by the service', () => {
|
it('should return an observable of false if the provided bistream is not in the list returned by the service', () => {
|
||||||
const format = new BitstreamFormat();
|
const format = new BitstreamFormat();
|
||||||
format.uuid = 'new';
|
format.uuid = 'new';
|
||||||
|
|
||||||
const result = comp.isSelected(format);
|
comp.selectedBitstreamFormatIDs().subscribe((selectedBitstreamFormatIDs: string[]) => {
|
||||||
|
expect(selectedBitstreamFormatIDs).not.toContain(format.id);
|
||||||
expect(result).toBeObservable(cold('b', { b: false }));
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -216,8 +212,6 @@ describe('BitstreamFormatsComponent', () => {
|
|||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
notificationsServiceStub = new NotificationsServiceStub();
|
notificationsServiceStub = new NotificationsServiceStub();
|
||||||
|
|
||||||
scheduler = getTestScheduler();
|
|
||||||
|
|
||||||
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
|
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
|
||||||
findAll: observableOf(mockFormatsRD),
|
findAll: observableOf(mockFormatsRD),
|
||||||
find: createSuccessfulRemoteDataObject$(mockFormatsList[0]),
|
find: createSuccessfulRemoteDataObject$(mockFormatsList[0]),
|
||||||
@@ -265,8 +259,6 @@ describe('BitstreamFormatsComponent', () => {
|
|||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
notificationsServiceStub = new NotificationsServiceStub();
|
notificationsServiceStub = new NotificationsServiceStub();
|
||||||
|
|
||||||
scheduler = getTestScheduler();
|
|
||||||
|
|
||||||
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
|
bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
|
||||||
findAll: observableOf(mockFormatsRD),
|
findAll: observableOf(mockFormatsRD),
|
||||||
find: createSuccessfulRemoteDataObject$(mockFormatsList[0]),
|
find: createSuccessfulRemoteDataObject$(mockFormatsList[0]),
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { combineLatest as observableCombineLatest, Observable} from 'rxjs';
|
import { Observable} from 'rxjs';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||||
@@ -7,7 +7,6 @@ import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
|
|||||||
import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
|
import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
|
||||||
import { map, mergeMap, switchMap, take, toArray } from 'rxjs/operators';
|
import { map, mergeMap, switchMap, take, toArray } from 'rxjs/operators';
|
||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { NoContent } from '../../../core/shared/NoContent.model';
|
import { NoContent } from '../../../core/shared/NoContent.model';
|
||||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||||
@@ -26,7 +25,12 @@ export class BitstreamFormatsComponent implements OnInit, OnDestroy {
|
|||||||
/**
|
/**
|
||||||
* A paginated list of bitstream formats to be shown on the page
|
* A paginated list of bitstream formats to be shown on the page
|
||||||
*/
|
*/
|
||||||
bitstreamFormats: Observable<RemoteData<PaginatedList<BitstreamFormat>>>;
|
bitstreamFormats$: Observable<RemoteData<PaginatedList<BitstreamFormat>>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently selected {@link BitstreamFormat} IDs
|
||||||
|
*/
|
||||||
|
selectedBitstreamFormatIDs$: Observable<string[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current pagination configuration for the page
|
* The current pagination configuration for the page
|
||||||
@@ -39,7 +43,6 @@ export class BitstreamFormatsComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
|
|
||||||
constructor(private notificationsService: NotificationsService,
|
constructor(private notificationsService: NotificationsService,
|
||||||
private router: Router,
|
|
||||||
private translateService: TranslateService,
|
private translateService: TranslateService,
|
||||||
private bitstreamFormatService: BitstreamFormatDataService,
|
private bitstreamFormatService: BitstreamFormatDataService,
|
||||||
private paginationService: PaginationService,
|
private paginationService: PaginationService,
|
||||||
@@ -94,14 +97,11 @@ export class BitstreamFormatsComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether a given bitstream format is selected in the list (checkbox)
|
* Returns the list of all the bitstream formats that are selected in the list (checkbox)
|
||||||
* @param bitstreamFormat
|
|
||||||
*/
|
*/
|
||||||
isSelected(bitstreamFormat: BitstreamFormat): Observable<boolean> {
|
selectedBitstreamFormatIDs(): Observable<string[]> {
|
||||||
return this.bitstreamFormatService.getSelectedBitstreamFormats().pipe(
|
return this.bitstreamFormatService.getSelectedBitstreamFormats().pipe(
|
||||||
map((bitstreamFormats: BitstreamFormat[]) => {
|
map((bitstreamFormats: BitstreamFormat[]) => bitstreamFormats.map((selectedFormat) => selectedFormat.id)),
|
||||||
return bitstreamFormats.find((selectedFormat) => selectedFormat.id === bitstreamFormat.id) != null;
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,27 +125,23 @@ export class BitstreamFormatsComponent implements OnInit, OnDestroy {
|
|||||||
const prefix = 'admin.registries.bitstream-formats.delete';
|
const prefix = 'admin.registries.bitstream-formats.delete';
|
||||||
const suffix = success ? 'success' : 'failure';
|
const suffix = success ? 'success' : 'failure';
|
||||||
|
|
||||||
const messages = observableCombineLatest(
|
const head: string = this.translateService.instant(`${prefix}.${suffix}.head`);
|
||||||
this.translateService.get(`${prefix}.${suffix}.head`),
|
const content: string = this.translateService.instant(`${prefix}.${suffix}.amount`, { amount: amount });
|
||||||
this.translateService.get(`${prefix}.${suffix}.amount`, {amount: amount})
|
|
||||||
);
|
|
||||||
messages.subscribe(([head, content]) => {
|
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
this.notificationsService.success(head, content);
|
this.notificationsService.success(head, content);
|
||||||
} else {
|
} else {
|
||||||
this.notificationsService.error(head, content);
|
this.notificationsService.error(head, content);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.bitstreamFormats$ = this.paginationService.getFindListOptions(this.pageConfig.id, this.pageConfig).pipe(
|
||||||
this.bitstreamFormats = this.paginationService.getFindListOptions(this.pageConfig.id, this.pageConfig).pipe(
|
|
||||||
switchMap((findListOptions: FindListOptions) => {
|
switchMap((findListOptions: FindListOptions) => {
|
||||||
return this.bitstreamFormatService.findAll(findListOptions);
|
return this.bitstreamFormatService.findAll(findListOptions);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
this.selectedBitstreamFormatIDs$ = this.selectedBitstreamFormatIDs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -27,14 +27,15 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let schema of (metadataSchemas | async)?.payload?.page"
|
<tr *ngFor="let schema of (metadataSchemas | async)?.payload?.page"
|
||||||
[ngClass]="{'table-primary' : isActive(schema) | async}">
|
[ngClass]="{'table-primary' : (activeMetadataSchema$ | async)?.id === schema.id}">
|
||||||
<td>
|
<td>
|
||||||
<label class="mb-0">
|
<label class="mb-0">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
[checked]="isSelected(schema) | async"
|
[checked]="(selectedMetadataSchemaIDs$ | async)?.includes(schema.id)"
|
||||||
(change)="selectMetadataSchema(schema, $event)"
|
(change)="selectMetadataSchema(schema, $event)"
|
||||||
>
|
>
|
||||||
<span class="sr-only">{{((isSelected(schema) | async) ? 'admin.registries.metadata.schemas.deselect' : 'admin.registries.metadata.schemas.select') | translate}}</span>
|
<span class="sr-only">{{(((selectedMetadataSchemaIDs$ | async)?.includes(schema.id)) ? 'admin.registries.metadata.schemas.deselect' : 'admin.registries.metadata.schemas.select') | translate}}</span>
|
||||||
|
|
||||||
</label>
|
</label>
|
||||||
</td>
|
</td>
|
||||||
<td class="selectable-row" (click)="editSchema(schema)"><a [routerLink]="[schema.prefix]">{{schema.id}}</a></td>
|
<td class="selectable-row" (click)="editSchema(schema)"><a [routerLink]="[schema.prefix]">{{schema.id}}</a></td>
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { MetadataRegistryComponent } from './metadata-registry.component';
|
import { MetadataRegistryComponent } from './metadata-registry.component';
|
||||||
import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { buildPaginatedList } from '../../../core/data/paginated-list.model';
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
@@ -15,18 +14,21 @@ import { HostWindowService } from '../../../shared/host-window.service';
|
|||||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
|
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
|
||||||
import { RestResponse } from '../../../core/cache/response.models';
|
|
||||||
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
|
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||||
|
import { RegistryServiceStub } from '../../../shared/testing/registry.service.stub';
|
||||||
|
import { createPaginatedList } from '../../../shared/testing/utils.test';
|
||||||
|
|
||||||
describe('MetadataRegistryComponent', () => {
|
describe('MetadataRegistryComponent', () => {
|
||||||
let comp: MetadataRegistryComponent;
|
let comp: MetadataRegistryComponent;
|
||||||
let fixture: ComponentFixture<MetadataRegistryComponent>;
|
let fixture: ComponentFixture<MetadataRegistryComponent>;
|
||||||
let registryService: RegistryService;
|
|
||||||
let paginationService;
|
let paginationService: PaginationServiceStub;
|
||||||
const mockSchemasList = [
|
let registryService: RegistryServiceStub;
|
||||||
|
|
||||||
|
const mockSchemasList: MetadataSchema[] = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
_links: {
|
_links: {
|
||||||
@@ -47,32 +49,18 @@ describe('MetadataRegistryComponent', () => {
|
|||||||
prefix: 'mock',
|
prefix: 'mock',
|
||||||
namespace: 'http://dspace.org/mockschema'
|
namespace: 'http://dspace.org/mockschema'
|
||||||
}
|
}
|
||||||
];
|
] as MetadataSchema[];
|
||||||
const mockSchemas = createSuccessfulRemoteDataObject$(buildPaginatedList(null, mockSchemasList));
|
|
||||||
/* eslint-disable no-empty,@typescript-eslint/no-empty-function */
|
|
||||||
const registryServiceStub = {
|
|
||||||
getMetadataSchemas: () => mockSchemas,
|
|
||||||
getActiveMetadataSchema: () => observableOf(undefined),
|
|
||||||
getSelectedMetadataSchemas: () => observableOf([]),
|
|
||||||
editMetadataSchema: (schema) => {
|
|
||||||
},
|
|
||||||
cancelEditMetadataSchema: () => {
|
|
||||||
},
|
|
||||||
deleteMetadataSchema: () => observableOf(new RestResponse(true, 200, 'OK')),
|
|
||||||
deselectAllMetadataSchema: () => {
|
|
||||||
},
|
|
||||||
clearMetadataSchemaRequests: () => observableOf(undefined)
|
|
||||||
};
|
|
||||||
/* eslint-enable no-empty, @typescript-eslint/no-empty-function */
|
|
||||||
|
|
||||||
paginationService = new PaginationServiceStub();
|
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
|
paginationService = new PaginationServiceStub();
|
||||||
|
registryService = new RegistryServiceStub();
|
||||||
|
spyOn(registryService, 'getMetadataSchemas').and.returnValue(createSuccessfulRemoteDataObject$(createPaginatedList(mockSchemasList)));
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule],
|
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule],
|
||||||
declarations: [MetadataRegistryComponent, PaginationComponent, EnumKeysPipe],
|
declarations: [MetadataRegistryComponent, PaginationComponent, EnumKeysPipe],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: RegistryService, useValue: registryServiceStub },
|
{ provide: RegistryService, useValue: registryService },
|
||||||
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) },
|
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) },
|
||||||
{ provide: PaginationService, useValue: paginationService },
|
{ provide: PaginationService, useValue: paginationService },
|
||||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() }
|
{ provide: NotificationsService, useValue: new NotificationsServiceStub() }
|
||||||
@@ -123,7 +111,7 @@ describe('MetadataRegistryComponent', () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should cancel editing the selected schema when clicked again', waitForAsync(() => {
|
it('should cancel editing the selected schema when clicked again', waitForAsync(() => {
|
||||||
spyOn(registryService, 'getActiveMetadataSchema').and.returnValue(observableOf(mockSchemasList[0] as MetadataSchema));
|
comp.activeMetadataSchema$ = observableOf(mockSchemasList[0] as MetadataSchema);
|
||||||
spyOn(registryService, 'cancelEditMetadataSchema');
|
spyOn(registryService, 'cancelEditMetadataSchema');
|
||||||
row.click();
|
row.click();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@@ -138,7 +126,7 @@ describe('MetadataRegistryComponent', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(registryService, 'deleteMetadataSchema').and.callThrough();
|
spyOn(registryService, 'deleteMetadataSchema').and.callThrough();
|
||||||
spyOn(registryService, 'getSelectedMetadataSchemas').and.returnValue(observableOf(selectedSchemas as MetadataSchema[]));
|
comp.selectedMetadataSchemaIDs$ = observableOf(selectedSchemas.map((selectedSchema: MetadataSchema) => selectedSchema.id));
|
||||||
comp.deleteSchemas();
|
comp.deleteSchemas();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
@@ -1,13 +1,11 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||||
import { RegistryService } from '../../../core/registry/registry.service';
|
import { RegistryService } from '../../../core/registry/registry.service';
|
||||||
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, zip } from 'rxjs';
|
import { BehaviorSubject, Observable, zip, Subscription } from 'rxjs';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||||
import { filter, map, switchMap, take } from 'rxjs/operators';
|
import { filter, map, switchMap, take } from 'rxjs/operators';
|
||||||
import { hasValue } from '../../../shared/empty.util';
|
|
||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
|
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
|
||||||
import { toFindListOptions } from '../../../shared/pagination/pagination.utils';
|
import { toFindListOptions } from '../../../shared/pagination/pagination.utils';
|
||||||
@@ -24,13 +22,23 @@ import { PaginationService } from '../../../core/pagination/pagination.service';
|
|||||||
* A component used for managing all existing metadata schemas within the repository.
|
* A component used for managing all existing metadata schemas within the repository.
|
||||||
* The admin can create, edit or delete metadata schemas here.
|
* The admin can create, edit or delete metadata schemas here.
|
||||||
*/
|
*/
|
||||||
export class MetadataRegistryComponent {
|
export class MetadataRegistryComponent implements OnDestroy, OnInit {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of all the current metadata schemas within the repository
|
* A list of all the current metadata schemas within the repository
|
||||||
*/
|
*/
|
||||||
metadataSchemas: Observable<RemoteData<PaginatedList<MetadataSchema>>>;
|
metadataSchemas: Observable<RemoteData<PaginatedList<MetadataSchema>>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link MetadataSchema}that is being edited
|
||||||
|
*/
|
||||||
|
activeMetadataSchema$: Observable<MetadataSchema>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The selected {@link MetadataSchema} IDs
|
||||||
|
*/
|
||||||
|
selectedMetadataSchemaIDs$: Observable<number[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pagination config used to display the list of metadata schemas
|
* Pagination config used to display the list of metadata schemas
|
||||||
*/
|
*/
|
||||||
@@ -40,15 +48,25 @@ export class MetadataRegistryComponent {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the list of MetadataSchemas needs an update
|
* Whether the list of MetadataSchemas needs an update
|
||||||
*/
|
*/
|
||||||
needsUpdate$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
needsUpdate$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
||||||
|
|
||||||
constructor(private registryService: RegistryService,
|
subscriptions: Subscription[] = [];
|
||||||
private notificationsService: NotificationsService,
|
|
||||||
private router: Router,
|
constructor(
|
||||||
private paginationService: PaginationService,
|
protected registryService: RegistryService,
|
||||||
private translateService: TranslateService) {
|
protected notificationsService: NotificationsService,
|
||||||
|
protected paginationService: PaginationService,
|
||||||
|
protected translateService: TranslateService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.activeMetadataSchema$ = this.registryService.getActiveMetadataSchema();
|
||||||
|
this.selectedMetadataSchemaIDs$ = this.registryService.getSelectedMetadataSchemas().pipe(
|
||||||
|
map((schemas: MetadataSchema[]) => schemas.map((schema: MetadataSchema) => schema.id)),
|
||||||
|
);
|
||||||
this.updateSchemas();
|
this.updateSchemas();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,30 +95,13 @@ export class MetadataRegistryComponent {
|
|||||||
* @param schema
|
* @param schema
|
||||||
*/
|
*/
|
||||||
editSchema(schema: MetadataSchema) {
|
editSchema(schema: MetadataSchema) {
|
||||||
this.getActiveSchema().pipe(take(1)).subscribe((activeSchema) => {
|
this.subscriptions.push(this.activeMetadataSchema$.pipe(take(1)).subscribe((activeSchema: MetadataSchema) => {
|
||||||
if (schema === activeSchema) {
|
if (schema === activeSchema) {
|
||||||
this.registryService.cancelEditMetadataSchema();
|
this.registryService.cancelEditMetadataSchema();
|
||||||
} else {
|
} else {
|
||||||
this.registryService.editMetadataSchema(schema);
|
this.registryService.editMetadataSchema(schema);
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether the given metadata schema is active (being edited)
|
|
||||||
* @param schema
|
|
||||||
*/
|
|
||||||
isActive(schema: MetadataSchema): Observable<boolean> {
|
|
||||||
return this.getActiveSchema().pipe(
|
|
||||||
map((activeSchema) => schema === activeSchema)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the active metadata schema (being edited)
|
|
||||||
*/
|
|
||||||
getActiveSchema(): Observable<MetadataSchema> {
|
|
||||||
return this.registryService.getActiveMetadataSchema();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -114,42 +115,25 @@ export class MetadataRegistryComponent {
|
|||||||
this.registryService.deselectMetadataSchema(schema);
|
this.registryService.deselectMetadataSchema(schema);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether a given metadata schema is selected in the list (checkbox)
|
|
||||||
* @param schema
|
|
||||||
*/
|
|
||||||
isSelected(schema: MetadataSchema): Observable<boolean> {
|
|
||||||
return this.registryService.getSelectedMetadataSchemas().pipe(
|
|
||||||
map((schemas) => schemas.find((selectedSchema) => selectedSchema === schema) != null)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete all the selected metadata schemas
|
* Delete all the selected metadata schemas
|
||||||
*/
|
*/
|
||||||
deleteSchemas() {
|
deleteSchemas() {
|
||||||
this.registryService.getSelectedMetadataSchemas().pipe(take(1)).subscribe(
|
this.subscriptions.push(this.selectedMetadataSchemaIDs$.pipe(
|
||||||
(schemas) => {
|
take(1),
|
||||||
const tasks$ = [];
|
switchMap((schemaIDs: number[]) => zip(schemaIDs.map((schemaID: number) => this.registryService.deleteMetadataSchema(schemaID).pipe(getFirstCompletedRemoteData())))),
|
||||||
for (const schema of schemas) {
|
).subscribe((responses: RemoteData<NoContent>[]) => {
|
||||||
if (hasValue(schema.id)) {
|
const successResponses: RemoteData<NoContent>[] = responses.filter((response: RemoteData<NoContent>) => response.hasSucceeded);
|
||||||
tasks$.push(this.registryService.deleteMetadataSchema(schema.id).pipe(getFirstCompletedRemoteData()));
|
const failedResponses: RemoteData<NoContent>[] = responses.filter((response: RemoteData<NoContent>) => response.hasFailed);
|
||||||
}
|
if (successResponses.length > 0) {
|
||||||
}
|
this.showNotification(true, successResponses.length);
|
||||||
zip(...tasks$).subscribe((responses: 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);
|
|
||||||
}
|
|
||||||
if (failedResponses.length > 0) {
|
|
||||||
this.showNotification(false, failedResponses.length);
|
|
||||||
}
|
|
||||||
this.registryService.deselectAllMetadataSchema();
|
|
||||||
this.registryService.cancelEditMetadataSchema();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
);
|
if (failedResponses.length > 0) {
|
||||||
|
this.showNotification(false, failedResponses.length);
|
||||||
|
}
|
||||||
|
this.registryService.deselectAllMetadataSchema();
|
||||||
|
this.registryService.cancelEditMetadataSchema();
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -160,20 +144,20 @@ export class MetadataRegistryComponent {
|
|||||||
showNotification(success: boolean, amount: number) {
|
showNotification(success: boolean, amount: number) {
|
||||||
const prefix = 'admin.registries.schema.notification';
|
const prefix = 'admin.registries.schema.notification';
|
||||||
const suffix = success ? 'success' : 'failure';
|
const suffix = success ? 'success' : 'failure';
|
||||||
const messages = observableCombineLatest(
|
|
||||||
this.translateService.get(success ? `${prefix}.${suffix}` : `${prefix}.${suffix}`),
|
const head: string = this.translateService.instant(success ? `${prefix}.${suffix}` : `${prefix}.${suffix}`);
|
||||||
this.translateService.get(`${prefix}.deleted.${suffix}`, {amount: amount})
|
const content: string = this.translateService.instant(`${prefix}.deleted.${suffix}`, {amount: amount});
|
||||||
);
|
|
||||||
messages.subscribe(([head, content]) => {
|
if (success) {
|
||||||
if (success) {
|
this.notificationsService.success(head, content);
|
||||||
this.notificationsService.success(head, content);
|
} else {
|
||||||
} else {
|
this.notificationsService.error(head, content);
|
||||||
this.notificationsService.error(head, content);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.paginationService.clearPagination(this.config.id);
|
this.paginationService.clearPagination(this.config.id);
|
||||||
|
this.subscriptions.map((subscription: Subscription) => subscription.unsubscribe());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<div *ngIf="registryService.getActiveMetadataSchema() | async; then editheader; else createHeader"></div>
|
<div *ngIf="activeMetadataSchema$ | async; then editheader; else createHeader"></div>
|
||||||
|
|
||||||
<ng-template #createHeader>
|
<ng-template #createHeader>
|
||||||
<h2>{{messagePrefix + '.create' | translate}}</h2>
|
<h2>{{messagePrefix + '.create' | translate}}</h2>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { MetadataSchemaFormComponent } from './metadata-schema-form.component';
|
import { MetadataSchemaFormComponent } from './metadata-schema-form.component';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
@@ -10,41 +10,26 @@ import { RegistryService } from '../../../../core/registry/registry.service';
|
|||||||
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
|
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model';
|
import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model';
|
||||||
|
import { RegistryServiceStub } from '../../../../shared/testing/registry.service.stub';
|
||||||
|
import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder-service.mock';
|
||||||
|
|
||||||
describe('MetadataSchemaFormComponent', () => {
|
describe('MetadataSchemaFormComponent', () => {
|
||||||
let component: MetadataSchemaFormComponent;
|
let component: MetadataSchemaFormComponent;
|
||||||
let fixture: ComponentFixture<MetadataSchemaFormComponent>;
|
let fixture: ComponentFixture<MetadataSchemaFormComponent>;
|
||||||
let registryService: RegistryService;
|
|
||||||
|
|
||||||
/* eslint-disable no-empty,@typescript-eslint/no-empty-function */
|
let registryService: RegistryServiceStub;
|
||||||
const registryServiceStub = {
|
|
||||||
getActiveMetadataSchema: () => observableOf(undefined),
|
|
||||||
createOrUpdateMetadataSchema: (schema: MetadataSchema) => observableOf(schema),
|
|
||||||
cancelEditMetadataSchema: () => {
|
|
||||||
},
|
|
||||||
clearMetadataSchemaRequests: () => observableOf(undefined)
|
|
||||||
};
|
|
||||||
const formBuilderServiceStub = {
|
|
||||||
createFormGroup: () => {
|
|
||||||
return {
|
|
||||||
patchValue: () => {
|
|
||||||
},
|
|
||||||
reset(_value?: any, _options?: { onlySelf?: boolean; emitEvent?: boolean; }): void {
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
/* eslint-enable no-empty, @typescript-eslint/no-empty-function */
|
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
|
registryService = new RegistryServiceStub();
|
||||||
|
|
||||||
return TestBed.configureTestingModule({
|
return TestBed.configureTestingModule({
|
||||||
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule],
|
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule],
|
||||||
declarations: [MetadataSchemaFormComponent, EnumKeysPipe],
|
declarations: [MetadataSchemaFormComponent, EnumKeysPipe],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: RegistryService, useValue: registryServiceStub },
|
{ provide: RegistryService, useValue: registryService },
|
||||||
{ provide: FormBuilderService, useValue: formBuilderServiceStub }
|
{ provide: FormBuilderService, useValue: getMockFormBuilderService() }
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -75,7 +60,7 @@ describe('MetadataSchemaFormComponent', () => {
|
|||||||
|
|
||||||
describe('without an active schema', () => {
|
describe('without an active schema', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(registryService, 'getActiveMetadataSchema').and.returnValue(observableOf(undefined));
|
component.activeMetadataSchema$ = observableOf(undefined);
|
||||||
component.onSubmit();
|
component.onSubmit();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
@@ -94,7 +79,7 @@ describe('MetadataSchemaFormComponent', () => {
|
|||||||
} as MetadataSchema);
|
} as MetadataSchema);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(registryService, 'getActiveMetadataSchema').and.returnValue(observableOf(expectedWithId));
|
component.activeMetadataSchema$ = observableOf(expectedWithId);
|
||||||
component.onSubmit();
|
component.onSubmit();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
@@ -8,10 +8,10 @@ import {
|
|||||||
import { UntypedFormGroup } from '@angular/forms';
|
import { UntypedFormGroup } from '@angular/forms';
|
||||||
import { RegistryService } from '../../../../core/registry/registry.service';
|
import { RegistryService } from '../../../../core/registry/registry.service';
|
||||||
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
|
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
|
||||||
import { switchMap, take, tap } from 'rxjs/operators';
|
import { map, switchMap, take } from 'rxjs/operators';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Observable, combineLatest } from 'rxjs';
|
|
||||||
import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model';
|
import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model';
|
||||||
|
import { Subscription, Observable } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-metadata-schema-form',
|
selector: 'ds-metadata-schema-form',
|
||||||
@@ -73,64 +73,71 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
@Output() submitForm: EventEmitter<any> = new EventEmitter();
|
@Output() submitForm: EventEmitter<any> = new EventEmitter();
|
||||||
|
|
||||||
constructor(public registryService: RegistryService, private formBuilderService: FormBuilderService, private translateService: TranslateService) {
|
/**
|
||||||
|
* The {@link MetadataSchema} that is currently being edited
|
||||||
|
*/
|
||||||
|
activeMetadataSchema$: Observable<MetadataSchema>;
|
||||||
|
|
||||||
|
subscriptions: Subscription[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected registryService: RegistryService,
|
||||||
|
protected formBuilderService: FormBuilderService,
|
||||||
|
protected translateService: TranslateService,
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
combineLatest([
|
this.name = new DynamicInputModel({
|
||||||
this.translateService.get(`${this.messagePrefix}.name`),
|
id: 'name',
|
||||||
this.translateService.get(`${this.messagePrefix}.namespace`)
|
label: this.translateService.instant(`${this.messagePrefix}.name`),
|
||||||
]).subscribe(([name, namespace]) => {
|
name: 'name',
|
||||||
this.name = new DynamicInputModel({
|
validators: {
|
||||||
id: 'name',
|
required: null,
|
||||||
label: name,
|
pattern: '^[^. ,]*$',
|
||||||
name: 'name',
|
maxLength: 32,
|
||||||
validators: {
|
},
|
||||||
required: null,
|
required: true,
|
||||||
pattern: '^[^. ,]*$',
|
errorMessages: {
|
||||||
maxLength: 32,
|
pattern: 'error.validation.metadata.name.invalid-pattern',
|
||||||
},
|
maxLength: 'error.validation.metadata.name.max-length',
|
||||||
required: true,
|
},
|
||||||
errorMessages: {
|
|
||||||
pattern: 'error.validation.metadata.name.invalid-pattern',
|
|
||||||
maxLength: 'error.validation.metadata.name.max-length',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.namespace = new DynamicInputModel({
|
|
||||||
id: 'namespace',
|
|
||||||
label: namespace,
|
|
||||||
name: 'namespace',
|
|
||||||
validators: {
|
|
||||||
required: null,
|
|
||||||
maxLength: 256,
|
|
||||||
},
|
|
||||||
required: true,
|
|
||||||
errorMessages: {
|
|
||||||
maxLength: 'error.validation.metadata.namespace.max-length',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.formModel = [
|
|
||||||
new DynamicFormGroupModel(
|
|
||||||
{
|
|
||||||
id: 'metadatadataschemagroup',
|
|
||||||
group:[this.namespace, this.name]
|
|
||||||
})
|
|
||||||
];
|
|
||||||
this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
|
|
||||||
this.registryService.getActiveMetadataSchema().subscribe((schema: MetadataSchema) => {
|
|
||||||
if (schema == null) {
|
|
||||||
this.clearFields();
|
|
||||||
} else {
|
|
||||||
this.formGroup.patchValue({
|
|
||||||
metadatadataschemagroup: {
|
|
||||||
name: schema.prefix,
|
|
||||||
namespace: schema.namespace,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.name.disabled = true;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
this.namespace = new DynamicInputModel({
|
||||||
|
id: 'namespace',
|
||||||
|
label: this.translateService.instant(`${this.messagePrefix}.namespace`),
|
||||||
|
name: 'namespace',
|
||||||
|
validators: {
|
||||||
|
required: null,
|
||||||
|
maxLength: 256,
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
errorMessages: {
|
||||||
|
maxLength: 'error.validation.metadata.namespace.max-length',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.formModel = [
|
||||||
|
new DynamicFormGroupModel(
|
||||||
|
{
|
||||||
|
id: 'metadatadataschemagroup',
|
||||||
|
group:[this.namespace, this.name]
|
||||||
|
})
|
||||||
|
];
|
||||||
|
this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
|
||||||
|
this.activeMetadataSchema$ = this.registryService.getActiveMetadataSchema();
|
||||||
|
this.subscriptions.push(this.activeMetadataSchema$.subscribe((schema: MetadataSchema) => {
|
||||||
|
if (schema == null) {
|
||||||
|
this.clearFields();
|
||||||
|
} else {
|
||||||
|
this.formGroup.patchValue({
|
||||||
|
metadatadataschemagroup: {
|
||||||
|
name: schema.prefix,
|
||||||
|
namespace: schema.namespace,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.name.disabled = true;
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -147,48 +154,29 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy {
|
|||||||
* Emit the updated/created schema using the EventEmitter submitForm
|
* Emit the updated/created schema using the EventEmitter submitForm
|
||||||
*/
|
*/
|
||||||
onSubmit(): void {
|
onSubmit(): void {
|
||||||
this.registryService
|
this.activeMetadataSchema$.pipe(
|
||||||
.getActiveMetadataSchema()
|
take(1),
|
||||||
.pipe(
|
switchMap((schema: MetadataSchema) => {
|
||||||
take(1),
|
const metadataValues = {
|
||||||
switchMap((schema: MetadataSchema) => {
|
prefix: this.name.value,
|
||||||
const metadataValues = {
|
namespace: this.namespace.value,
|
||||||
prefix: this.name.value,
|
};
|
||||||
namespace: this.namespace.value,
|
if (schema == null) {
|
||||||
};
|
return this.registryService.createOrUpdateMetadataSchema(Object.assign(new MetadataSchema(), metadataValues));
|
||||||
|
} else {
|
||||||
let createOrUpdate$: Observable<MetadataSchema>;
|
return this.registryService.createOrUpdateMetadataSchema(Object.assign(new MetadataSchema(), schema, {
|
||||||
|
namespace: metadataValues.namespace,
|
||||||
if (schema == null) {
|
}));
|
||||||
createOrUpdate$ =
|
}
|
||||||
this.registryService.createOrUpdateMetadataSchema(
|
}),
|
||||||
Object.assign(new MetadataSchema(), metadataValues)
|
switchMap((updatedOrCreatedSchema: MetadataSchema) => this.registryService.clearMetadataSchemaRequests().pipe(
|
||||||
);
|
map(() => updatedOrCreatedSchema),
|
||||||
} else {
|
)),
|
||||||
const updatedSchema = Object.assign(
|
).subscribe((updatedOrCreatedSchema: MetadataSchema) => {
|
||||||
new MetadataSchema(),
|
this.submitForm.emit(updatedOrCreatedSchema);
|
||||||
schema,
|
this.clearFields();
|
||||||
{
|
this.registryService.cancelEditMetadataSchema();
|
||||||
namespace: metadataValues.namespace,
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
createOrUpdate$ =
|
|
||||||
this.registryService.createOrUpdateMetadataSchema(
|
|
||||||
updatedSchema
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return createOrUpdate$;
|
|
||||||
}),
|
|
||||||
tap(() => {
|
|
||||||
this.registryService.clearMetadataSchemaRequests().subscribe();
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.subscribe((updatedOrCreatedSchema: MetadataSchema) => {
|
|
||||||
this.submitForm.emit(updatedOrCreatedSchema);
|
|
||||||
this.clearFields();
|
|
||||||
this.registryService.cancelEditMetadataSchema();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -204,5 +192,6 @@ export class MetadataSchemaFormComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.onCancel();
|
this.onCancel();
|
||||||
|
this.subscriptions.forEach((subscription: Subscription) => subscription.unsubscribe());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,14 +9,17 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { EnumKeysPipe } from '../../../../shared/utils/enum-keys-pipe';
|
import { EnumKeysPipe } from '../../../../shared/utils/enum-keys-pipe';
|
||||||
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
|
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
import { MetadataField } from '../../../../core/metadata/metadata-field.model';
|
import { MetadataField } from '../../../../core/metadata/metadata-field.model';
|
||||||
import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model';
|
import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model';
|
||||||
|
import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder-service.mock';
|
||||||
|
import { RegistryServiceStub } from '../../../../shared/testing/registry.service.stub';
|
||||||
|
|
||||||
describe('MetadataFieldFormComponent', () => {
|
describe('MetadataFieldFormComponent', () => {
|
||||||
let component: MetadataFieldFormComponent;
|
let component: MetadataFieldFormComponent;
|
||||||
let fixture: ComponentFixture<MetadataFieldFormComponent>;
|
let fixture: ComponentFixture<MetadataFieldFormComponent>;
|
||||||
let registryService: RegistryService;
|
|
||||||
|
let registryService: RegistryServiceStub;
|
||||||
|
|
||||||
const metadataSchema = Object.assign(new MetadataSchema(), {
|
const metadataSchema = Object.assign(new MetadataSchema(), {
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -24,38 +27,17 @@ describe('MetadataFieldFormComponent', () => {
|
|||||||
prefix: 'fake'
|
prefix: 'fake'
|
||||||
});
|
});
|
||||||
|
|
||||||
/* eslint-disable no-empty,@typescript-eslint/no-empty-function */
|
|
||||||
const registryServiceStub = {
|
|
||||||
getActiveMetadataField: () => observableOf(undefined),
|
|
||||||
createMetadataField: (field: MetadataField) => observableOf(field),
|
|
||||||
updateMetadataField: (field: MetadataField) => observableOf(field),
|
|
||||||
cancelEditMetadataField: () => {
|
|
||||||
},
|
|
||||||
cancelEditMetadataSchema: () => {
|
|
||||||
},
|
|
||||||
clearMetadataFieldRequests: () => observableOf(undefined)
|
|
||||||
};
|
|
||||||
const formBuilderServiceStub = {
|
|
||||||
createFormGroup: () => {
|
|
||||||
return {
|
|
||||||
patchValue: () => {
|
|
||||||
},
|
|
||||||
reset(_value?: any, _options?: { onlySelf?: boolean; emitEvent?: boolean; }): void {
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
/* eslint-enable no-empty, @typescript-eslint/no-empty-function */
|
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
|
registryService = new RegistryServiceStub();
|
||||||
|
|
||||||
return TestBed.configureTestingModule({
|
return TestBed.configureTestingModule({
|
||||||
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule],
|
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule],
|
||||||
declarations: [MetadataFieldFormComponent, EnumKeysPipe],
|
declarations: [MetadataFieldFormComponent, EnumKeysPipe],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: RegistryService, useValue: registryServiceStub },
|
{ provide: RegistryService, useValue: registryService },
|
||||||
{ provide: FormBuilderService, useValue: formBuilderServiceStub }
|
{ provide: FormBuilderService, useValue: getMockFormBuilderService() }
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@@ -31,8 +31,8 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let field of fields?.page"
|
<tr *ngFor="let field of fields?.page"
|
||||||
[ngClass]="{'table-primary' : isActive(field) | async}">
|
[ngClass]="{'table-primary' : (activeField$ | async)?.id === field.id}">
|
||||||
<td *ngVar="(isSelected(field) | async) as selected">
|
<td *ngVar="(selectedMetadataFieldIDs$ | async)?.includes(field.id) as selected">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
[attr.aria-label]="(selected ? 'admin.registries.schema.fields.deselect' : 'admin.registries.schema.fields.select') | translate"
|
[attr.aria-label]="(selected ? 'admin.registries.schema.fields.deselect' : 'admin.registries.schema.fields.select') | translate"
|
||||||
[checked]="selected"
|
[checked]="selected"
|
||||||
|
@@ -4,7 +4,7 @@ import { of as observableOf } from 'rxjs';
|
|||||||
import { buildPaginatedList } from '../../../core/data/paginated-list.model';
|
import { buildPaginatedList } from '../../../core/data/paginated-list.model';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { RegistryService } from '../../../core/registry/registry.service';
|
import { RegistryService } from '../../../core/registry/registry.service';
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
@@ -12,25 +12,28 @@ import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe';
|
|||||||
import { PaginationComponent } from '../../../shared/pagination/pagination.component';
|
import { PaginationComponent } from '../../../shared/pagination/pagination.component';
|
||||||
import { HostWindowServiceStub } from '../../../shared/testing/host-window-service.stub';
|
import { HostWindowServiceStub } from '../../../shared/testing/host-window-service.stub';
|
||||||
import { HostWindowService } from '../../../shared/host-window.service';
|
import { HostWindowService } from '../../../shared/host-window.service';
|
||||||
import { RouterStub } from '../../../shared/testing/router.stub';
|
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
|
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
|
||||||
import { RestResponse } from '../../../core/cache/response.models';
|
|
||||||
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
|
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
|
||||||
import { MetadataField } from '../../../core/metadata/metadata-field.model';
|
import { MetadataField } from '../../../core/metadata/metadata-field.model';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
import { VarDirective } from '../../../shared/utils/var.directive';
|
import { VarDirective } from '../../../shared/utils/var.directive';
|
||||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||||
|
import { RegistryServiceStub } from '../../../shared/testing/registry.service.stub';
|
||||||
|
|
||||||
describe('MetadataSchemaComponent', () => {
|
describe('MetadataSchemaComponent', () => {
|
||||||
let comp: MetadataSchemaComponent;
|
let comp: MetadataSchemaComponent;
|
||||||
let fixture: ComponentFixture<MetadataSchemaComponent>;
|
let fixture: ComponentFixture<MetadataSchemaComponent>;
|
||||||
let registryService: RegistryService;
|
|
||||||
const mockSchemasList = [
|
let registryService: RegistryServiceStub;
|
||||||
|
let activatedRoute: ActivatedRouteStub;
|
||||||
|
let paginationService: PaginationServiceStub;
|
||||||
|
|
||||||
|
const mockSchemasList: MetadataSchema[] = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
_links: {
|
_links: {
|
||||||
@@ -51,8 +54,8 @@ describe('MetadataSchemaComponent', () => {
|
|||||||
prefix: 'mock',
|
prefix: 'mock',
|
||||||
namespace: 'http://dspace.org/mockschema'
|
namespace: 'http://dspace.org/mockschema'
|
||||||
}
|
}
|
||||||
];
|
] as MetadataSchema[];
|
||||||
const mockFieldsList = [
|
const mockFieldsList: MetadataField[] = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
_links: {
|
_links: {
|
||||||
@@ -101,47 +104,29 @@ describe('MetadataSchemaComponent', () => {
|
|||||||
scopeNote: null,
|
scopeNote: null,
|
||||||
schema: createSuccessfulRemoteDataObject$(mockSchemasList[1])
|
schema: createSuccessfulRemoteDataObject$(mockSchemasList[1])
|
||||||
}
|
}
|
||||||
];
|
] as MetadataField[];
|
||||||
const mockSchemas = createSuccessfulRemoteDataObject$(buildPaginatedList(null, mockSchemasList));
|
|
||||||
/* eslint-disable no-empty,@typescript-eslint/no-empty-function */
|
|
||||||
const registryServiceStub = {
|
|
||||||
getMetadataSchemas: () => mockSchemas,
|
|
||||||
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([]),
|
|
||||||
editMetadataField: (schema) => {
|
|
||||||
},
|
|
||||||
cancelEditMetadataField: () => {
|
|
||||||
},
|
|
||||||
deleteMetadataField: () => observableOf(new RestResponse(true, 200, 'OK')),
|
|
||||||
deselectAllMetadataField: () => {
|
|
||||||
},
|
|
||||||
clearMetadataFieldRequests: () => observableOf(undefined)
|
|
||||||
};
|
|
||||||
/* eslint-enable no-empty, @typescript-eslint/no-empty-function */
|
|
||||||
const schemaNameParam = 'mock';
|
const schemaNameParam = 'mock';
|
||||||
const activatedRouteStub = Object.assign(new ActivatedRouteStub(), {
|
|
||||||
params: observableOf({
|
|
||||||
schemaName: schemaNameParam
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const paginationService = new PaginationServiceStub();
|
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
|
activatedRoute = new ActivatedRouteStub({
|
||||||
|
schemaName: schemaNameParam,
|
||||||
|
});
|
||||||
|
paginationService = new PaginationServiceStub();
|
||||||
|
registryService = new RegistryServiceStub();
|
||||||
|
spyOn(registryService, 'getMetadataFieldsBySchema').and.returnValue(createSuccessfulRemoteDataObject$(buildPaginatedList(null, mockFieldsList.filter((value) => value.id === 3 || value.id === 4))));
|
||||||
|
spyOn(registryService, 'getMetadataSchemaByPrefix').and.callFake((schemaName) => createSuccessfulRemoteDataObject$(mockSchemasList.filter((value) => value.prefix === schemaName)[0]));
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule],
|
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule],
|
||||||
declarations: [MetadataSchemaComponent, PaginationComponent, EnumKeysPipe, VarDirective],
|
declarations: [MetadataSchemaComponent, PaginationComponent, EnumKeysPipe, VarDirective],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: RegistryService, useValue: registryServiceStub },
|
{ provide: RegistryService, useValue: registryService },
|
||||||
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
{ provide: ActivatedRoute, useValue: activatedRoute },
|
||||||
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) },
|
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) },
|
||||||
{ provide: Router, useValue: new RouterStub() },
|
|
||||||
{ provide: PaginationService, useValue: paginationService },
|
{ provide: PaginationService, useValue: paginationService },
|
||||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() }
|
{ provide: NotificationsService, useValue: new NotificationsServiceStub() }
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -190,7 +175,7 @@ describe('MetadataSchemaComponent', () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
it('should cancel editing the selected field when clicked again', waitForAsync(() => {
|
it('should cancel editing the selected field when clicked again', waitForAsync(() => {
|
||||||
spyOn(registryService, 'getActiveMetadataField').and.returnValue(observableOf(mockFieldsList[2] as MetadataField));
|
comp.activeField$ = observableOf(mockFieldsList[2] as MetadataField);
|
||||||
spyOn(registryService, 'cancelEditMetadataField');
|
spyOn(registryService, 'cancelEditMetadataField');
|
||||||
row.click();
|
row.click();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@@ -205,7 +190,7 @@ describe('MetadataSchemaComponent', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(registryService, 'deleteMetadataField').and.callThrough();
|
spyOn(registryService, 'deleteMetadataField').and.callThrough();
|
||||||
spyOn(registryService, 'getSelectedMetadataFields').and.returnValue(observableOf(selectedFields as MetadataField[]));
|
comp.selectedMetadataFieldIDs$ = observableOf(selectedFields.map((metadataField: MetadataField) => metadataField.id));
|
||||||
comp.deleteFields();
|
comp.deleteFields();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
@@ -3,17 +3,16 @@ import { RegistryService } from '../../../core/registry/registry.service';
|
|||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import {
|
import {
|
||||||
BehaviorSubject,
|
BehaviorSubject,
|
||||||
combineLatest as observableCombineLatest,
|
|
||||||
combineLatest,
|
combineLatest,
|
||||||
Observable,
|
Observable,
|
||||||
of as observableOf,
|
of as observableOf,
|
||||||
zip
|
zip,
|
||||||
|
Subscription,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||||
import { map, switchMap, take } from 'rxjs/operators';
|
import { map, switchMap, take } from 'rxjs/operators';
|
||||||
import { hasValue } from '../../../shared/empty.util';
|
|
||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { MetadataField } from '../../../core/metadata/metadata-field.model';
|
import { MetadataField } from '../../../core/metadata/metadata-field.model';
|
||||||
@@ -32,7 +31,7 @@ import { PaginationService } from '../../../core/pagination/pagination.service';
|
|||||||
* A component used for managing all existing metadata fields within the current metadata schema.
|
* A component used for managing all existing metadata fields within the current metadata schema.
|
||||||
* The admin can create, edit or delete metadata fields here.
|
* The admin can create, edit or delete metadata fields here.
|
||||||
*/
|
*/
|
||||||
export class MetadataSchemaComponent implements OnInit, OnDestroy {
|
export class MetadataSchemaComponent implements OnDestroy, OnInit {
|
||||||
/**
|
/**
|
||||||
* The metadata schema
|
* The metadata schema
|
||||||
*/
|
*/
|
||||||
@@ -57,26 +56,33 @@ export class MetadataSchemaComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
needsUpdate$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
needsUpdate$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
||||||
|
|
||||||
constructor(private registryService: RegistryService,
|
/**
|
||||||
private route: ActivatedRoute,
|
* The current {@link MetadataField} that is being edited
|
||||||
private notificationsService: NotificationsService,
|
*/
|
||||||
private paginationService: PaginationService,
|
activeField$: Observable<MetadataField>;
|
||||||
private translateService: TranslateService) {
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The selected {@link MetadataField} IDs
|
||||||
|
*/
|
||||||
|
selectedMetadataFieldIDs$: Observable<number[]>;
|
||||||
|
|
||||||
|
subscriptions: Subscription[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected registryService: RegistryService,
|
||||||
|
protected route: ActivatedRoute,
|
||||||
|
protected notificationsService: NotificationsService,
|
||||||
|
protected paginationService: PaginationService,
|
||||||
|
protected translateService: TranslateService,
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.route.params.subscribe((params) => {
|
this.metadataSchema$ = this.registryService.getMetadataSchemaByPrefix(this.route.snapshot.params.schemaName).pipe(getFirstSucceededRemoteDataPayload());
|
||||||
this.initialize(params);
|
this.activeField$ = this.registryService.getActiveMetadataField();
|
||||||
});
|
this.selectedMetadataFieldIDs$ = this.registryService.getSelectedMetadataFields().pipe(
|
||||||
}
|
map((metadataFields: MetadataField[]) => metadataFields.map((metadataField: MetadataField) => metadataField.id)),
|
||||||
|
);
|
||||||
/**
|
|
||||||
* Initialize the component using the params within the url (schemaName)
|
|
||||||
* @param params
|
|
||||||
*/
|
|
||||||
initialize(params) {
|
|
||||||
this.metadataSchema$ = this.registryService.getMetadataSchemaByPrefix(params.schemaName).pipe(getFirstSucceededRemoteDataPayload());
|
|
||||||
this.updateFields();
|
this.updateFields();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,30 +115,13 @@ export class MetadataSchemaComponent implements OnInit, OnDestroy {
|
|||||||
* @param field
|
* @param field
|
||||||
*/
|
*/
|
||||||
editField(field: MetadataField) {
|
editField(field: MetadataField) {
|
||||||
this.getActiveField().pipe(take(1)).subscribe((activeField) => {
|
this.subscriptions.push(this.activeField$.pipe(take(1)).subscribe((activeField) => {
|
||||||
if (field === activeField) {
|
if (field === activeField) {
|
||||||
this.registryService.cancelEditMetadataField();
|
this.registryService.cancelEditMetadataField();
|
||||||
} else {
|
} else {
|
||||||
this.registryService.editMetadataField(field);
|
this.registryService.editMetadataField(field);
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether the given metadata field is active (being edited)
|
|
||||||
* @param field
|
|
||||||
*/
|
|
||||||
isActive(field: MetadataField): Observable<boolean> {
|
|
||||||
return this.getActiveField().pipe(
|
|
||||||
map((activeField) => field === activeField)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the active metadata field (being edited)
|
|
||||||
*/
|
|
||||||
getActiveField(): Observable<MetadataField> {
|
|
||||||
return this.registryService.getActiveMetadataField();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -146,42 +135,25 @@ export class MetadataSchemaComponent implements OnInit, OnDestroy {
|
|||||||
this.registryService.deselectMetadataField(field);
|
this.registryService.deselectMetadataField(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether a given metadata field is selected in the list (checkbox)
|
|
||||||
* @param field
|
|
||||||
*/
|
|
||||||
isSelected(field: MetadataField): Observable<boolean> {
|
|
||||||
return this.registryService.getSelectedMetadataFields().pipe(
|
|
||||||
map((fields) => fields.find((selectedField) => selectedField === field) != null)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete all the selected metadata fields
|
* Delete all the selected metadata fields
|
||||||
*/
|
*/
|
||||||
deleteFields() {
|
deleteFields() {
|
||||||
this.registryService.getSelectedMetadataFields().pipe(take(1)).subscribe(
|
this.subscriptions.push(this.selectedMetadataFieldIDs$.pipe(
|
||||||
(fields) => {
|
take(1),
|
||||||
const tasks$ = [];
|
switchMap((fieldIDs) => zip(fieldIDs.map((fieldID) => this.registryService.deleteMetadataField(fieldID).pipe(getFirstCompletedRemoteData())))),
|
||||||
for (const field of fields) {
|
).subscribe((responses: RemoteData<NoContent>[]) => {
|
||||||
if (hasValue(field.id)) {
|
const successResponses = responses.filter((response: RemoteData<NoContent>) => response.hasSucceeded);
|
||||||
tasks$.push(this.registryService.deleteMetadataField(field.id).pipe(getFirstCompletedRemoteData()));
|
const failedResponses = responses.filter((response: RemoteData<NoContent>) => response.hasFailed);
|
||||||
}
|
if (successResponses.length > 0) {
|
||||||
}
|
this.showNotification(true, successResponses.length);
|
||||||
zip(...tasks$).subscribe((responses: 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);
|
|
||||||
}
|
|
||||||
if (failedResponses.length > 0) {
|
|
||||||
this.showNotification(false, failedResponses.length);
|
|
||||||
}
|
|
||||||
this.registryService.deselectAllMetadataField();
|
|
||||||
this.registryService.cancelEditMetadataField();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
);
|
if (failedResponses.length > 0) {
|
||||||
|
this.showNotification(false, failedResponses.length);
|
||||||
|
}
|
||||||
|
this.registryService.deselectAllMetadataField();
|
||||||
|
this.registryService.cancelEditMetadataField();
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -192,21 +164,19 @@ export class MetadataSchemaComponent implements OnInit, OnDestroy {
|
|||||||
showNotification(success: boolean, amount: number) {
|
showNotification(success: boolean, amount: number) {
|
||||||
const prefix = 'admin.registries.schema.notification';
|
const prefix = 'admin.registries.schema.notification';
|
||||||
const suffix = success ? 'success' : 'failure';
|
const suffix = success ? 'success' : 'failure';
|
||||||
const messages = observableCombineLatest([
|
const head = this.translateService.instant(success ? `${prefix}.${suffix}` : `${prefix}.${suffix}`);
|
||||||
this.translateService.get(success ? `${prefix}.${suffix}` : `${prefix}.${suffix}`),
|
const content = this.translateService.instant(`${prefix}.field.deleted.${suffix}`, { amount: amount });
|
||||||
this.translateService.get(`${prefix}.field.deleted.${suffix}`, { amount: amount })
|
if (success) {
|
||||||
]);
|
this.notificationsService.success(head, content);
|
||||||
messages.subscribe(([head, content]) => {
|
} else {
|
||||||
if (success) {
|
this.notificationsService.error(head, content);
|
||||||
this.notificationsService.success(head, content);
|
}
|
||||||
} else {
|
|
||||||
this.notificationsService.error(head, content);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.paginationService.clearPagination(this.config.id);
|
this.paginationService.clearPagination(this.config.id);
|
||||||
this.registryService.deselectAllMetadataField();
|
this.registryService.deselectAllMetadataField();
|
||||||
|
this.subscriptions.forEach((subscription: Subscription) => subscription.unsubscribe());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
|
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
|
||||||
<ds-themed-thumbnail [thumbnail]="item?.thumbnail | async"></ds-themed-thumbnail>
|
<ds-themed-thumbnail [thumbnail]="item?.thumbnail | async"></ds-themed-thumbnail>
|
||||||
</ds-metadata-field-wrapper>
|
</ds-metadata-field-wrapper>
|
||||||
<ng-container *ngVar="(getFiles() | async) as bitstreams">
|
<ng-container *ngIf="(bitstreams$ | async) as bitstreams">
|
||||||
<ds-metadata-field-wrapper [label]="('item.page.files' | translate)">
|
<ds-metadata-field-wrapper [label]="('item.page.files' | translate)">
|
||||||
<div *ngIf="bitstreams?.length > 0" class="file-section">
|
<div *ngIf="bitstreams?.length > 0" class="file-section">
|
||||||
<button class="btn btn-link" *ngFor="let file of bitstreams; let last=last;" (click)="downloadBitstreamFile(file?.uuid)">
|
<button class="btn btn-link" *ngFor="let file of bitstreams; let last=last;" (click)="downloadBitstreamFile(file?.uuid)">
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||||
|
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { first } from 'rxjs/operators';
|
import { first } from 'rxjs/operators';
|
||||||
@@ -13,6 +13,7 @@ import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service
|
|||||||
import { SearchResult } from '../../../search/models/search-result.model';
|
import { SearchResult } from '../../../search/models/search-result.model';
|
||||||
import { Context } from '../../../../core/shared/context.model';
|
import { Context } from '../../../../core/shared/context.model';
|
||||||
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { hasValue } from '../../../empty.util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component show metadata for the given item object in the detail view.
|
* This component show metadata for the given item object in the detail view.
|
||||||
@@ -23,7 +24,7 @@ import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
|
|||||||
templateUrl: './item-detail-preview.component.html',
|
templateUrl: './item-detail-preview.component.html',
|
||||||
animations: [fadeInOut]
|
animations: [fadeInOut]
|
||||||
})
|
})
|
||||||
export class ItemDetailPreviewComponent {
|
export class ItemDetailPreviewComponent implements OnChanges {
|
||||||
/**
|
/**
|
||||||
* The item to display
|
* The item to display
|
||||||
*/
|
*/
|
||||||
@@ -62,6 +63,12 @@ export class ItemDetailPreviewComponent {
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
if (hasValue(changes.item)) {
|
||||||
|
this.bitstreams$ = this.getFiles();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform bitstream download
|
* Perform bitstream download
|
||||||
*/
|
*/
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div *ngIf="!hidePaginationDetail && collectionSize > 0" class="col-auto pagination-info">
|
<div *ngIf="!hidePaginationDetail && collectionSize > 0" class="col-auto pagination-info">
|
||||||
<span class="align-middle hidden-xs-down">{{ 'pagination.showing.label' | translate }}</span>
|
<span class="align-middle hidden-xs-down">{{ 'pagination.showing.label' | translate }}</span>
|
||||||
<span class="align-middle">{{ 'pagination.showing.detail' | translate:(getShowingDetails(collectionSize)|async)}}</span>
|
<span class="align-middle">{{ 'pagination.showing.detail' | translate:(showingDetails$ | async)}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div *ngIf="!hideGear" ngbDropdown #paginationControls="ngbDropdown" placement="bottom-right" class="d-inline-block float-right">
|
<div *ngIf="!hideGear" ngbDropdown #paginationControls="ngbDropdown" placement="bottom-right" class="d-inline-block float-right">
|
||||||
|
@@ -4,25 +4,32 @@ import {
|
|||||||
Component,
|
Component,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
Input,
|
Input,
|
||||||
|
OnChanges,
|
||||||
OnDestroy,
|
OnDestroy,
|
||||||
OnInit,
|
OnInit,
|
||||||
Output,
|
Output,
|
||||||
ViewEncapsulation
|
SimpleChanges,
|
||||||
|
ViewEncapsulation,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
|
||||||
import { Observable, of as observableOf, Subscription } from 'rxjs';
|
import { Observable, of as observableOf, Subscription, switchMap } from 'rxjs';
|
||||||
|
|
||||||
import { HostWindowService } from '../host-window.service';
|
import { HostWindowService } from '../host-window.service';
|
||||||
import { PaginationComponentOptions } from './pagination-component-options.model';
|
import { PaginationComponentOptions } from './pagination-component-options.model';
|
||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
import { hasValue } from '../empty.util';
|
import { hasValue, hasValueOperator } from '../empty.util';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
import { map, take } from 'rxjs/operators';
|
import { map, take, startWith } from 'rxjs/operators';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../core/data/paginated-list.model';
|
||||||
import { ListableObject } from '../object-collection/shared/listable-object.model';
|
import { ListableObject } from '../object-collection/shared/listable-object.model';
|
||||||
import { ViewMode } from '../../core/shared/view-mode.model';
|
import { ViewMode } from '../../core/shared/view-mode.model';
|
||||||
|
|
||||||
|
interface PaginationDetails {
|
||||||
|
range: string;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default pagination controls component.
|
* The default pagination controls component.
|
||||||
*/
|
*/
|
||||||
@@ -34,7 +41,7 @@ import { ViewMode } from '../../core/shared/view-mode.model';
|
|||||||
changeDetection: ChangeDetectionStrategy.Default,
|
changeDetection: ChangeDetectionStrategy.Default,
|
||||||
encapsulation: ViewEncapsulation.Emulated
|
encapsulation: ViewEncapsulation.Emulated
|
||||||
})
|
})
|
||||||
export class PaginationComponent implements OnDestroy, OnInit {
|
export class PaginationComponent implements OnChanges, OnDestroy, OnInit {
|
||||||
/**
|
/**
|
||||||
* ViewMode that should be passed to {@link ListableObjectComponentLoaderComponent}.
|
* ViewMode that should be passed to {@link ListableObjectComponentLoaderComponent}.
|
||||||
*/
|
*/
|
||||||
@@ -174,6 +181,9 @@ export class PaginationComponent implements OnDestroy, OnInit {
|
|||||||
public sortField$: Observable<string>;
|
public sortField$: Observable<string>;
|
||||||
public defaultSortField = 'name';
|
public defaultSortField = 'name';
|
||||||
|
|
||||||
|
|
||||||
|
public showingDetails$: Observable<PaginationDetails>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array to track all subscriptions and unsubscribe them onDestroy
|
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||||
* @type {Array}
|
* @type {Array}
|
||||||
@@ -202,6 +212,12 @@ export class PaginationComponent implements OnDestroy, OnInit {
|
|||||||
this.initializeConfig();
|
this.initializeConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
if (changes.collectionSize.currentValue !== changes.collectionSize.previousValue) {
|
||||||
|
this.showingDetails$ = this.getShowingDetails(this.collectionSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method provided by Angular. Invoked when the instance is destroyed.
|
* Method provided by Angular. Invoked when the instance is destroyed.
|
||||||
*/
|
*/
|
||||||
@@ -239,9 +255,11 @@ export class PaginationComponent implements OnDestroy, OnInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(private cdRef: ChangeDetectorRef,
|
constructor(
|
||||||
private paginationService: PaginationService,
|
protected cdRef: ChangeDetectorRef,
|
||||||
public hostWindowService: HostWindowService) {
|
protected paginationService: PaginationService,
|
||||||
|
public hostWindowService: HostWindowService,
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -295,26 +313,31 @@ export class PaginationComponent implements OnDestroy, OnInit {
|
|||||||
/**
|
/**
|
||||||
* Method to get pagination details of the current viewed page.
|
* Method to get pagination details of the current viewed page.
|
||||||
*/
|
*/
|
||||||
public getShowingDetails(collectionSize: number): Observable<any> {
|
public getShowingDetails(collectionSize: number): Observable<PaginationDetails> {
|
||||||
let showingDetails = observableOf({ range: null + ' - ' + null, total: null });
|
return observableOf(collectionSize).pipe(
|
||||||
if (collectionSize) {
|
hasValueOperator(),
|
||||||
showingDetails = this.paginationService.getCurrentPagination(this.id, this.paginationOptions).pipe(
|
switchMap(() => this.paginationService.getCurrentPagination(this.id, this.paginationOptions)),
|
||||||
map((currentPaginationOptions) => {
|
map((currentPaginationOptions) => {
|
||||||
let firstItem: number;
|
let firstItem: number;
|
||||||
let lastItem: number;
|
let lastItem: number;
|
||||||
const pageMax = currentPaginationOptions.pageSize * currentPaginationOptions.currentPage;
|
const pageMax = currentPaginationOptions.pageSize * currentPaginationOptions.currentPage;
|
||||||
|
|
||||||
firstItem = currentPaginationOptions.pageSize * (currentPaginationOptions.currentPage - 1) + 1;
|
firstItem = currentPaginationOptions.pageSize * (currentPaginationOptions.currentPage - 1) + 1;
|
||||||
if (collectionSize > pageMax) {
|
if (collectionSize > pageMax) {
|
||||||
lastItem = pageMax;
|
lastItem = pageMax;
|
||||||
} else {
|
} else {
|
||||||
lastItem = collectionSize;
|
lastItem = collectionSize;
|
||||||
}
|
}
|
||||||
return {range: firstItem + ' - ' + lastItem, total: collectionSize};
|
return {
|
||||||
})
|
range: `${firstItem} - ${lastItem}`,
|
||||||
);
|
total: collectionSize,
|
||||||
}
|
};
|
||||||
return showingDetails;
|
}),
|
||||||
|
startWith({
|
||||||
|
range: `${null} - ${null}`,
|
||||||
|
total: null,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
107
src/app/shared/testing/registry.service.stub.ts
Normal file
107
src/app/shared/testing/registry.service.stub.ts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/* eslint-disable no-empty,@typescript-eslint/no-empty-function */
|
||||||
|
import { FindListOptions } from '../../core/data/find-list-options.model';
|
||||||
|
import { FollowLinkConfig } from '../utils/follow-link-config.model';
|
||||||
|
import { MetadataSchema } from '../../core/metadata/metadata-schema.model';
|
||||||
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
|
import { PaginatedList } from '../../core/data/paginated-list.model';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils';
|
||||||
|
import { createPaginatedList } from './utils.test';
|
||||||
|
import { MetadataField } from '../../core/metadata/metadata-field.model';
|
||||||
|
import { NoContent } from '../../core/shared/NoContent.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stub class of {@link RegistryService}
|
||||||
|
*/
|
||||||
|
export class RegistryServiceStub {
|
||||||
|
|
||||||
|
getMetadataSchemas(_options: FindListOptions = {}, _useCachedVersionIfAvailable = true, _reRequestOnStale = true, ..._linksToFollow: FollowLinkConfig<MetadataSchema>[]): Observable<RemoteData<PaginatedList<MetadataSchema>>> {
|
||||||
|
return createSuccessfulRemoteDataObject$(createPaginatedList());
|
||||||
|
}
|
||||||
|
|
||||||
|
getMetadataSchemaByPrefix(_prefix: string, _useCachedVersionIfAvailable = true, _reRequestOnStale = true, ..._linksToFollow: FollowLinkConfig<MetadataSchema>[]): Observable<RemoteData<MetadataSchema>> {
|
||||||
|
return createSuccessfulRemoteDataObject$(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
getMetadataFieldsBySchema(_schema: MetadataSchema, _options: FindListOptions = {}, _useCachedVersionIfAvailable = true, _reRequestOnStale = true, ..._linksToFollow: FollowLinkConfig<MetadataField>[]): Observable<RemoteData<PaginatedList<MetadataField>>> {
|
||||||
|
return createSuccessfulRemoteDataObject$(createPaginatedList());
|
||||||
|
}
|
||||||
|
|
||||||
|
editMetadataSchema(_schema: MetadataSchema): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelEditMetadataSchema(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
getActiveMetadataSchema(): Observable<MetadataSchema> {
|
||||||
|
return observableOf(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectMetadataSchema(_schema: MetadataSchema): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
deselectMetadataSchema(_schema: MetadataSchema): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
deselectAllMetadataSchema(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelectedMetadataSchemas(): Observable<MetadataSchema[]> {
|
||||||
|
return observableOf([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
editMetadataField(_field: MetadataField): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelEditMetadataField(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
getActiveMetadataField(): Observable<MetadataField> {
|
||||||
|
return observableOf(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectMetadataField(_field: MetadataField): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
deselectMetadataField(_field: MetadataField): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
deselectAllMetadataField(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelectedMetadataFields(): Observable<MetadataField[]> {
|
||||||
|
return observableOf([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
createOrUpdateMetadataSchema(schema: MetadataSchema): Observable<MetadataSchema> {
|
||||||
|
return observableOf(schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteMetadataSchema(_id: number): Observable<RemoteData<NoContent>> {
|
||||||
|
return createSuccessfulRemoteDataObject$(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearMetadataSchemaRequests(): Observable<string> {
|
||||||
|
return observableOf('');
|
||||||
|
}
|
||||||
|
|
||||||
|
createMetadataField(field: MetadataField, _schema: MetadataSchema): Observable<MetadataField> {
|
||||||
|
return observableOf(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMetadataField(field: MetadataField): Observable<MetadataField> {
|
||||||
|
return observableOf(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteMetadataField(_id: number): Observable<RemoteData<NoContent>> {
|
||||||
|
return createSuccessfulRemoteDataObject$(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearMetadataFieldRequests(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
queryMetadataFields(_query: string, _options: FindListOptions = {}, _useCachedVersionIfAvailable = true, _reRequestOnStale = true, ..._linksToFollow: FollowLinkConfig<MetadataField>[]): Observable<RemoteData<PaginatedList<MetadataField>>> {
|
||||||
|
return createSuccessfulRemoteDataObject$(createPaginatedList());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user