117287: Removed method calls returning observables from the metadata schema registry

This commit is contained in:
Alexandre Vryghem
2024-09-05 11:49:18 +02:00
parent c74c178533
commit f03ed89687
5 changed files with 95 additions and 159 deletions

View File

@@ -9,14 +9,17 @@ import { TranslateModule } from '@ngx-translate/core';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { EnumKeysPipe } from '../../../../shared/utils/enum-keys-pipe';
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 { 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', () => {
let component: MetadataFieldFormComponent;
let fixture: ComponentFixture<MetadataFieldFormComponent>;
let registryService: RegistryService;
let registryService: RegistryServiceStub;
const metadataSchema = Object.assign(new MetadataSchema(), {
id: 1,
@@ -24,38 +27,17 @@ describe('MetadataFieldFormComponent', () => {
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(() => {
registryService = new RegistryServiceStub();
return TestBed.configureTestingModule({
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule],
declarations: [MetadataFieldFormComponent, EnumKeysPipe],
providers: [
{ provide: RegistryService, useValue: registryServiceStub },
{ provide: FormBuilderService, useValue: formBuilderServiceStub }
{ provide: RegistryService, useValue: registryService },
{ provide: FormBuilderService, useValue: getMockFormBuilderService() }
],
schemas: [NO_ERRORS_SCHEMA]
schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents();
}));

View File

@@ -32,11 +32,11 @@
</thead>
<tbody>
<tr *ngFor="let field of fields?.page"
[ngClass]="{'table-primary' : isActive(field) | async}">
[ngClass]="{'table-primary' : (activeField$ | async)?.id === field.id}">
<td>
<label class="mb-0">
<input type="checkbox"
[checked]="isSelected(field) | async"
[checked]="(selectedMetadataFieldIDs$ | async)?.includes(field.id)"
(change)="selectMetadataField(field, $event)">
</label>
</td>

View File

@@ -4,7 +4,7 @@ import { of as observableOf } from 'rxjs';
import { buildPaginatedList } from '../../../core/data/paginated-list.model';
import { TranslateModule } from '@ngx-translate/core';
import { CommonModule } from '@angular/common';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivatedRoute } from '@angular/router';
import { By } from '@angular/platform-browser';
import { RegistryService } from '../../../core/registry/registry.service';
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 { HostWindowServiceStub } from '../../../shared/testing/host-window-service.stub';
import { HostWindowService } from '../../../shared/host-window.service';
import { RouterStub } from '../../../shared/testing/router.stub';
import { RouterTestingModule } from '@angular/router/testing';
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 { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
import { RestResponse } from '../../../core/cache/response.models';
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
import { MetadataField } from '../../../core/metadata/metadata-field.model';
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
import { VarDirective } from '../../../shared/utils/var.directive';
import { PaginationService } from '../../../core/pagination/pagination.service';
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
import { RegistryServiceStub } from '../../../shared/testing/registry.service.stub';
describe('MetadataSchemaComponent', () => {
let comp: MetadataSchemaComponent;
let fixture: ComponentFixture<MetadataSchemaComponent>;
let registryService: RegistryService;
const mockSchemasList = [
let registryService: RegistryServiceStub;
let activatedRoute: ActivatedRouteStub;
let paginationService: PaginationServiceStub;
const mockSchemasList: MetadataSchema[] = [
{
id: 1,
_links: {
@@ -51,8 +54,8 @@ describe('MetadataSchemaComponent', () => {
prefix: 'mock',
namespace: 'http://dspace.org/mockschema'
}
];
const mockFieldsList = [
] as MetadataSchema[];
const mockFieldsList: MetadataField[] = [
{
id: 1,
_links: {
@@ -101,47 +104,29 @@ describe('MetadataSchemaComponent', () => {
scopeNote: null,
schema: createSuccessfulRemoteDataObject$(mockSchemasList[1])
}
];
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 */
] as MetadataField[];
const schemaNameParam = 'mock';
const activatedRouteStub = Object.assign(new ActivatedRouteStub(), {
params: observableOf({
schemaName: schemaNameParam
})
});
const paginationService = new PaginationServiceStub();
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({
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule],
declarations: [MetadataSchemaComponent, PaginationComponent, EnumKeysPipe, VarDirective],
providers: [
{ provide: RegistryService, useValue: registryServiceStub },
{ provide: ActivatedRoute, useValue: activatedRouteStub },
{ provide: RegistryService, useValue: registryService },
{ provide: ActivatedRoute, useValue: activatedRoute },
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) },
{ provide: Router, useValue: new RouterStub() },
{ provide: PaginationService, useValue: paginationService },
{ provide: NotificationsService, useValue: new NotificationsServiceStub() }
],
schemas: [NO_ERRORS_SCHEMA]
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents();
}));
@@ -190,7 +175,7 @@ describe('MetadataSchemaComponent', () => {
}));
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');
row.click();
fixture.detectChanges();
@@ -205,7 +190,7 @@ describe('MetadataSchemaComponent', () => {
beforeEach(() => {
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();
fixture.detectChanges();
});

View File

@@ -1,19 +1,18 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { RegistryService } from '../../../core/registry/registry.service';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivatedRoute } from '@angular/router';
import {
BehaviorSubject,
combineLatest as observableCombineLatest,
combineLatest,
Observable,
of as observableOf,
zip
zip,
Subscription,
} from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list.model';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { map, switchMap, take } from 'rxjs/operators';
import { hasValue } from '../../../shared/empty.util';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
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.
* The admin can create, edit or delete metadata fields here.
*/
export class MetadataSchemaComponent implements OnInit {
export class MetadataSchemaComponent implements OnDestroy, OnInit {
/**
* The metadata schema
*/
@@ -57,27 +56,33 @@ export class MetadataSchemaComponent implements OnInit {
*/
needsUpdate$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
constructor(private registryService: RegistryService,
private route: ActivatedRoute,
private notificationsService: NotificationsService,
private router: Router,
private paginationService: PaginationService,
private translateService: TranslateService) {
/**
* The current {@link MetadataField} that is being edited
*/
activeField$: Observable<MetadataField>;
/**
* 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 {
this.route.params.subscribe((params) => {
this.initialize(params);
});
}
/**
* Initialize the component using the params within the url (schemaName)
* @param params
*/
initialize(params) {
this.metadataSchema$ = this.registryService.getMetadataSchemaByPrefix(params.schemaName).pipe(getFirstSucceededRemoteDataPayload());
this.metadataSchema$ = this.registryService.getMetadataSchemaByPrefix(this.route.snapshot.params.schemaName).pipe(getFirstSucceededRemoteDataPayload());
this.activeField$ = this.registryService.getActiveMetadataField();
this.selectedMetadataFieldIDs$ = this.registryService.getSelectedMetadataFields().pipe(
map((metadataFields: MetadataField[]) => metadataFields.map((metadataField: MetadataField) => metadataField.id)),
);
this.updateFields();
}
@@ -86,7 +91,7 @@ export class MetadataSchemaComponent implements OnInit {
*/
private updateFields() {
this.metadataFields$ = this.paginationService.getCurrentPagination(this.config.id, this.config).pipe(
switchMap((currentPagination) => combineLatest(this.metadataSchema$, this.needsUpdate$, observableOf(currentPagination))),
switchMap((currentPagination) => combineLatest([this.metadataSchema$, this.needsUpdate$, observableOf(currentPagination)])),
switchMap(([schema, update, currentPagination]: [MetadataSchema, boolean, PaginationComponentOptions]) => {
if (update) {
this.needsUpdate$.next(false);
@@ -110,30 +115,13 @@ export class MetadataSchemaComponent implements OnInit {
* @param field
*/
editField(field: MetadataField) {
this.getActiveField().pipe(take(1)).subscribe((activeField) => {
this.subscriptions.push(this.activeField$.pipe(take(1)).subscribe((activeField) => {
if (field === activeField) {
this.registryService.cancelEditMetadataField();
} else {
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();
}));
}
/**
@@ -147,29 +135,14 @@ export class MetadataSchemaComponent implements OnInit {
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
*/
deleteFields() {
this.registryService.getSelectedMetadataFields().pipe(take(1)).subscribe(
(fields) => {
const tasks$ = [];
for (const field of fields) {
if (hasValue(field.id)) {
tasks$.push(this.registryService.deleteMetadataField(field.id).pipe(getFirstCompletedRemoteData()));
}
}
zip(...tasks$).subscribe((responses: RemoteData<NoContent>[]) => {
this.subscriptions.push(this.selectedMetadataFieldIDs$.pipe(
take(1),
switchMap((fieldIDs) => zip(fieldIDs.map((fieldID) => this.registryService.deleteMetadataField(fieldID).pipe(getFirstCompletedRemoteData())))),
).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) {
@@ -180,9 +153,7 @@ export class MetadataSchemaComponent implements OnInit {
}
this.registryService.deselectAllMetadataField();
this.registryService.cancelEditMetadataField();
});
}
);
}));
}
/**
@@ -193,20 +164,18 @@ export class MetadataSchemaComponent implements OnInit {
showNotification(success: boolean, amount: number) {
const prefix = 'admin.registries.schema.notification';
const suffix = success ? 'success' : 'failure';
const messages = observableCombineLatest(
this.translateService.get(success ? `${prefix}.${suffix}` : `${prefix}.${suffix}`),
this.translateService.get(`${prefix}.field.deleted.${suffix}`, { amount: amount })
);
messages.subscribe(([head, content]) => {
const head = this.translateService.instant(success ? `${prefix}.${suffix}` : `${prefix}.${suffix}`);
const content = this.translateService.instant(`${prefix}.field.deleted.${suffix}`, { amount: amount });
if (success) {
this.notificationsService.success(head, content);
} else {
this.notificationsService.error(head, content);
}
});
}
ngOnDestroy(): void {
this.paginationService.clearPagination(this.config.id);
this.subscriptions.forEach((subscription: Subscription) => subscription.unsubscribe());
}
}

View File

@@ -85,12 +85,12 @@ export class RegistryServiceStub {
return observableOf('');
}
createMetadataField(_field: MetadataField, _schema: MetadataSchema): Observable<MetadataField> {
return observableOf(undefined);
createMetadataField(field: MetadataField, _schema: MetadataSchema): Observable<MetadataField> {
return observableOf(field);
}
updateMetadataField(_field: MetadataField): Observable<MetadataField> {
return observableOf(undefined);
updateMetadataField(field: MetadataField): Observable<MetadataField> {
return observableOf(field);
}
deleteMetadataField(_id: number): Observable<RemoteData<NoContent>> {