diff --git a/src/app/clarin-licenses/clarin-license-table/clarin-license-table.component.html b/src/app/clarin-licenses/clarin-license-table/clarin-license-table.component.html index 729b0f781d5..77c6622e3ab 100644 --- a/src/app/clarin-licenses/clarin-license-table/clarin-license-table.component.html +++ b/src/app/clarin-licenses/clarin-license-table/clarin-license-table.component.html @@ -83,4 +83,66 @@ + +
+

{{'clarin.license.label.section.title' | translate}}

+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
{{'clarin.license.label.table.header.label' | translate}}{{'clarin.license.label.table.header.title' | translate}}{{'clarin.license.label.table.header.extended' | translate}}{{'clarin.license.label.table.header.icon' | translate}}{{'clarin.license.label.table.header.actions' | translate}}
{{label?.label}}{{label?.title}}{{label?.extended ? ('clarin.license.label.table.boolean.yes' | translate) : ('clarin.license.label.table.boolean.no' | translate)}} + + {{label?.icon?.length > 0 ? ('clarin.license.label.table.icon.available' | translate) : ('clarin.license.label.table.icon.none' | translate)}} + + + +
{{'clarin.license.label.table.empty' | translate}}
+
+ + + {{'clarin.license.label.table.loading' | translate}} +
+
+
diff --git a/src/app/clarin-licenses/clarin-license-table/clarin-license-table.component.scss b/src/app/clarin-licenses/clarin-license-table/clarin-license-table.component.scss index b4dab6de9bf..2ea1fa73dbf 100644 --- a/src/app/clarin-licenses/clarin-license-table/clarin-license-table.component.scss +++ b/src/app/clarin-licenses/clarin-license-table/clarin-license-table.component.scss @@ -15,3 +15,7 @@ width: 3.5%; max-width: 3.5%; } + +.labels-actions-column { + width: 11rem; +} diff --git a/src/app/clarin-licenses/clarin-license-table/clarin-license-table.component.spec.ts b/src/app/clarin-licenses/clarin-license-table/clarin-license-table.component.spec.ts index 0cc824bdd8b..0ff685adbde 100644 --- a/src/app/clarin-licenses/clarin-license-table/clarin-license-table.component.spec.ts +++ b/src/app/clarin-licenses/clarin-license-table/clarin-license-table.component.spec.ts @@ -1,9 +1,11 @@ -import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; import { ClarinLicenseTableComponent } from './clarin-license-table.component'; import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; import { ClarinLicenseDataService } from '../../core/data/clarin/clarin-license-data.service'; import { RequestService } from '../../core/data/request.service'; -import { of as observableOf } from 'rxjs'; +import { EventEmitter } from '@angular/core'; +import { of as observableOf, throwError } from 'rxjs'; import { SharedModule } from '../../shared/shared.module'; import { CommonModule } from '@angular/common'; import { ReactiveFormsModule } from '@angular/forms'; @@ -15,22 +17,27 @@ import { NotificationsService } from '../../shared/notifications/notifications.s import { defaultPagination } from '../clarin-license-table-pagination'; import { ClarinLicenseLabelDataService } from '../../core/data/clarin/clarin-license-label-data.service'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { HostWindowService } from '../../shared/host-window.service'; import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub'; import { createdLicenseLabelRD$, createdLicenseRD$, mockExtendedLicenseLabel, + mockLicenseLabelListRD$, mockLicense, mockLicenseRD$, mockNonExtendedLicenseLabel, successfulResponse } from '../../shared/testing/clarin-license-mock'; import {GroupDataService} from '../../core/eperson/group-data.service'; import {createSuccessfulRemoteDataObject$} from '../../shared/remote-data.utils'; +import { createFailedRemoteDataObject$, createNoContentRemoteDataObject$ } from '../../shared/remote-data.utils'; import {createPaginatedList} from '../../shared/testing/utils.test'; import {LinkHeadService} from '../../core/services/link-head.service'; import {ConfigurationDataService} from '../../core/data/configuration-data.service'; import {ConfigurationProperty} from '../../core/shared/configuration-property.model'; import {SearchConfigurationService} from '../../core/shared/search/search-configuration.service'; +import { DefineLicenseLabelFormComponent } from './modal/define-license-label-form/define-license-label-form.component'; +import { ConfirmationModalComponent } from '../../shared/confirmation-modal/confirmation-modal.component'; describe('ClarinLicenseTableComponent', () => { let component: ClarinLicenseTableComponent; @@ -40,10 +47,13 @@ describe('ClarinLicenseTableComponent', () => { let clarinLicenseLabelDataService: ClarinLicenseLabelDataService; let requestService: RequestService; let notificationService: NotificationsServiceStub; - let modalStub: NgbActiveModal; + let activeModalStub: NgbActiveModal; + let modalServiceStub: jasmine.SpyObj; let groupsDataService: GroupDataService; let service: ConfigurationDataService; let searchConfigurationServiceStub: SearchConfigurationService; + let labelEditModalRef: any; + let labelDeleteModalRef: any; beforeEach(async () => { notificationService = new NotificationsServiceStub(); @@ -55,14 +65,36 @@ describe('ClarinLicenseTableComponent', () => { getLinkPath: observableOf('') }); clarinLicenseLabelDataService = jasmine.createSpyObj('clarinLicenseLabelService', { - create: createdLicenseLabelRD$ + create: createdLicenseLabelRD$, + findAll: mockLicenseLabelListRD$, + put: createdLicenseLabelRD$, + delete: observableOf({ hasSucceeded: true }) }); requestService = jasmine.createSpyObj('requestService', { send: observableOf('response'), getByUUID: observableOf(successfulResponse), generateRequestId: observableOf('123456'), }); - modalStub = jasmine.createSpyObj('modalService', ['close', 'open']); + activeModalStub = jasmine.createSpyObj('activeModal', ['close', 'open']); + modalServiceStub = jasmine.createSpyObj('modalService', ['open']); + labelEditModalRef = { + componentInstance: {}, + result: Promise.resolve(null) + }; + labelDeleteModalRef = { + componentInstance: { + response: new EventEmitter() + } + }; + modalServiceStub.open.and.callFake((modalComponent) => { + if (modalComponent === DefineLicenseLabelFormComponent) { + return labelEditModalRef; + } + if (modalComponent === ConfirmationModalComponent) { + return labelDeleteModalRef; + } + return { componentInstance: {}, result: Promise.resolve(null) } as any; + }); groupsDataService = jasmine.createSpyObj('groupsDataService', { findListByHref: createSuccessfulRemoteDataObject$(createPaginatedList([])), getGroupRegistryRouterLink: '' @@ -101,7 +133,8 @@ describe('ClarinLicenseTableComponent', () => { { provide: ClarinLicenseLabelDataService, useValue: clarinLicenseLabelDataService }, { provide: PaginationService, useValue: new PaginationServiceStub() }, { provide: NotificationsService, useValue: notificationService }, - { provide: NgbActiveModal, useValue: modalStub }, + { provide: NgbActiveModal, useValue: activeModalStub }, + { provide: NgbModal, useValue: modalServiceStub }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, { provide: GroupDataService, useValue: groupsDataService }, { provide: LinkHeadService, useValue: linkHeadService }, @@ -181,4 +214,120 @@ describe('ClarinLicenseTableComponent', () => { expect((component as any).clarinLicenseService.searchBy).toHaveBeenCalled(); expect((component as ClarinLicenseTableComponent).licensesRD$).not.toBeNull(); }); + + describe('label edit flow', () => { + beforeEach(() => { + notificationService.success.calls.reset(); + notificationService.error.calls.reset(); + (clarinLicenseLabelDataService.put as jasmine.Spy).calls.reset(); + }); + + it('should open edit modal with the selected label when editLabel is called', () => { + component.editLabel(mockExtendedLicenseLabel); + + expect(modalServiceStub.open).toHaveBeenCalledWith(DefineLicenseLabelFormComponent); + expect(labelEditModalRef.componentInstance.clarinLicenseLabel).toBe(mockExtendedLicenseLabel); + }); + + it('should call clarinLicenseLabelService.put with updated label on modal submit', fakeAsync(() => { + const refreshSpy = spyOn(component, 'refreshLabels').and.stub(); + const reloadLicensesSpy = spyOn(component, 'loadAllLicenses').and.stub(); + labelEditModalRef.result = Promise.resolve({ + label: 'EDIT', + title: 'Edited title', + extended: false + }); + + component.editLabel(mockExtendedLicenseLabel); + tick(); + + expect((clarinLicenseLabelDataService.put as jasmine.Spy)).toHaveBeenCalled(); + const putArgument = (clarinLicenseLabelDataService.put as jasmine.Spy).calls.mostRecent().args[0]; + expect(putArgument.id).toBe(mockExtendedLicenseLabel.id); + expect(putArgument._links).toEqual(mockExtendedLicenseLabel._links); + expect(putArgument.label).toBe('EDIT'); + expect(putArgument.title).toBe('Edited title'); + expect(putArgument.extended).toBeFalse(); + expect(notificationService.success).toHaveBeenCalled(); + expect(refreshSpy).toHaveBeenCalled(); + expect(reloadLicensesSpy).toHaveBeenCalled(); + })); + + it('should show error notification on failed edit', fakeAsync(() => { + spyOn(component, 'refreshLabels').and.stub(); + (clarinLicenseLabelDataService.put as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$('put failed', 500)); + + component.editLicenseLabel({ + label: 'ERR', + title: 'Failed title', + extended: true + }, mockExtendedLicenseLabel); + tick(); + + expect(notificationService.error).toHaveBeenCalled(); + })); + }); + + describe('label delete flow', () => { + beforeEach(() => { + notificationService.success.calls.reset(); + notificationService.error.calls.reset(); + (clarinLicenseLabelDataService.delete as jasmine.Spy).calls.reset(); + labelDeleteModalRef.componentInstance.response = new EventEmitter(); + }); + + it('should open confirmation modal when confirmDeleteLabel is called', () => { + component.confirmDeleteLabel(mockNonExtendedLicenseLabel); + + expect(modalServiceStub.open).toHaveBeenCalledWith(ConfirmationModalComponent); + expect(labelDeleteModalRef.componentInstance.headerLabel).toBe('clarin.license.label.delete.confirm.title'); + expect(labelDeleteModalRef.componentInstance.infoLabel).toBe('clarin.license.label.delete.confirm.message'); + expect(labelDeleteModalRef.componentInstance.dso.name).toBe(mockNonExtendedLicenseLabel.label); + }); + + it('should call clarinLicenseLabelService.delete with correct id on confirmation', fakeAsync(() => { + const refreshSpy = spyOn(component, 'refreshLabels').and.stub(); + const reloadLicensesSpy = spyOn(component, 'loadAllLicenses').and.stub(); + (clarinLicenseLabelDataService.delete as jasmine.Spy).and.returnValue(createNoContentRemoteDataObject$()); + + component.confirmDeleteLabel(mockNonExtendedLicenseLabel); + labelDeleteModalRef.componentInstance.response.emit(true); + tick(); + + expect((clarinLicenseLabelDataService.delete as jasmine.Spy)).toHaveBeenCalledWith(String(mockNonExtendedLicenseLabel.id)); + expect(notificationService.success).toHaveBeenCalled(); + expect(refreshSpy).toHaveBeenCalled(); + expect(reloadLicensesSpy).toHaveBeenCalled(); + })); + + it('should show error notification on failed delete', () => { + spyOn(component, 'refreshLabels').and.stub(); + (clarinLicenseLabelDataService.delete as jasmine.Spy).and.returnValue(throwError(() => new Error('delete failed'))); + + component.confirmDeleteLabel(mockNonExtendedLicenseLabel); + labelDeleteModalRef.componentInstance.response.emit(true); + + expect(notificationService.error).toHaveBeenCalled(); + }); + + it('should not call delete service when confirmation is cancelled', () => { + component.confirmDeleteLabel(mockNonExtendedLicenseLabel); + labelDeleteModalRef.componentInstance.response.emit(false); + + expect((clarinLicenseLabelDataService.delete as jasmine.Spy)).not.toHaveBeenCalled(); + }); + }); + + describe('label row actions', () => { + it('should render edit and delete buttons for each label row', () => { + fixture.detectChanges(); + + const firstRowButtons = fixture.debugElement.queryAll(By.css('.labels-section tbody tr'))[0] + .queryAll(By.css('button')); + + expect(firstRowButtons.length).toBe(2); + expect((firstRowButtons[0].nativeElement as HTMLButtonElement).disabled).toBeFalse(); + expect((firstRowButtons[1].nativeElement as HTMLButtonElement).disabled).toBeFalse(); + }); + }); }); diff --git a/src/app/clarin-licenses/clarin-license-table/clarin-license-table.component.ts b/src/app/clarin-licenses/clarin-license-table/clarin-license-table.component.ts index 143f991aab1..1a8aaba78db 100644 --- a/src/app/clarin-licenses/clarin-license-table/clarin-license-table.component.ts +++ b/src/app/clarin-licenses/clarin-license-table/clarin-license-table.component.ts @@ -1,11 +1,11 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; -import { BehaviorSubject, combineLatest as observableCombineLatest } from 'rxjs'; +import { BehaviorSubject, combineLatest as observableCombineLatest, Subject } from 'rxjs'; import { RemoteData } from '../../core/data/remote-data'; import { PaginatedList } from '../../core/data/paginated-list.model'; import { ClarinLicense } from '../../core/shared/clarin/clarin-license.model'; import { getFirstCompletedRemoteData, getFirstSucceededRemoteData } from '../../core/shared/operators'; -import { scan, switchMap } from 'rxjs/operators'; +import { scan, switchMap, take, takeUntil } from 'rxjs/operators'; import { PaginationService } from '../../core/pagination/pagination.service'; import { ClarinLicenseDataService } from '../../core/data/clarin/clarin-license-data.service'; import { defaultPagination, defaultSortConfiguration } from '../clarin-license-table-pagination'; @@ -22,6 +22,9 @@ import { ClarinLicenseLabelExtendedSerializer } from '../../core/shared/clarin/c import { ClarinLicenseRequiredInfoSerializer } from '../../core/shared/clarin/clarin-license-required-info-serializer'; import cloneDeep from 'lodash/cloneDeep'; import { RequestParam } from '../../core/cache/models/request-param.model'; +import { SortOptions } from '../../core/cache/models/sort-options.model'; +import { ConfirmationModalComponent } from '../../shared/confirmation-modal/confirmation-modal.component'; +import { DSpaceObject } from '../../core/shared/dspace-object.model'; /** * Component for managing clarin licenses and defining clarin license labels. @@ -31,7 +34,14 @@ import { RequestParam } from '../../core/cache/models/request-param.model'; templateUrl: './clarin-license-table.component.html', styleUrls: ['./clarin-license-table.component.scss'] }) -export class ClarinLicenseTableComponent implements OnInit { +export class ClarinLicenseTableComponent implements OnInit, OnDestroy { + + private readonly defaultListState = { + searchTerm: '', + currentPage: 1, + currentPagination: defaultPagination, + currentSort: defaultSortConfiguration + }; constructor(private paginationService: PaginationService, private clarinLicenseService: ClarinLicenseDataService, @@ -67,9 +77,45 @@ export class ClarinLicenseTableComponent implements OnInit { */ searchingLicenseName = ''; + /** + * RemoteData stream for license labels table. + */ + labelsRD$: BehaviorSubject>> = + new BehaviorSubject>>(null); + + /** + * Loading state for labels table. + */ + loading$ = new BehaviorSubject(false); + + /** + * Pagination configuration for labels table. + */ + labelPaginationOptions: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { + id: 'cLicenseLabels', + currentPage: 1, + pageSize: 10 + }); + + /** + * Triggers a labels reload without changing pagination state. + */ + private labelsRefresh$ = new BehaviorSubject(undefined); + + /** + * Emits when component is destroyed to clean up subscriptions. + */ + private ngUnsubscribe = new Subject(); + ngOnInit(): void { this.initializePaginationOptions(); this.loadAllLicenses(); + this.initializeLabelsPaginationStream(); + } + + ngOnDestroy(): void { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); } // define license @@ -95,6 +141,7 @@ export class ClarinLicenseTableComponent implements OnInit { const errorMessageContentDef = 'clarin-license.define-license.notification.error-content'; if (isNull(clarinLicense)) { this.notifyOperationStatus(clarinLicense, successfulMessageContentDef, errorMessageContentDef); + return; } // convert string value from the form to the number @@ -147,6 +194,7 @@ export class ClarinLicenseTableComponent implements OnInit { const errorMessageContentDef = 'clarin-license.edit-license.notification.error-content'; if (isNull(clarinLicense)) { this.notifyOperationStatus(clarinLicense, successfulMessageContentDef, errorMessageContentDef); + return; } const clarinLicenseObj = new ClarinLicense(); @@ -212,10 +260,11 @@ export class ClarinLicenseTableComponent implements OnInit { * @param clarinLicenseLabel object from the License Label modal. */ defineLicenseLabel(clarinLicenseLabel: ClarinLicenseLabel) { - const successfulMessageContentDef = 'clarin-license-label.define-license-label.notification.successful-content'; - const errorMessageContentDef = 'clarin-license-label.define-license-label.notification.error-content'; + const successfulMessageContentDef = 'clarin.license.label.create.success'; + const errorMessageContentDef = 'clarin.license.label.create.error'; if (isNull(clarinLicenseLabel)) { this.notifyOperationStatus(clarinLicenseLabel, successfulMessageContentDef, errorMessageContentDef); + return; } // convert file to the byte array @@ -266,6 +315,7 @@ export class ClarinLicenseTableComponent implements OnInit { // check payload and show error or successful this.notifyOperationStatus(defineLicenseLabelResponse, successfulMessageContentDef, errorMessageContentDef); this.loadAllLicenses(); + this.refreshLabels(); }); } @@ -287,6 +337,136 @@ export class ClarinLicenseTableComponent implements OnInit { }); } + /** + * Open the edit modal for the selected license label, pre-filling its current values. + * On confirm, calls the PUT service and refreshes the label list. + */ + editLabel(label: ClarinLicenseLabel) { + if (isNull(label)) { + return; + } + + const editLabelModalRef = this.modalService.open(DefineLicenseLabelFormComponent); + editLabelModalRef.componentInstance.clarinLicenseLabel = label; + + editLabelModalRef.result.then((result) => { + this.editLicenseLabel(result, label); + }).catch(() => { /* dismissed */ }); + } + + /** + * Send a PUT request to update the selected label with the new form values. + * Handles success/error notifications and refreshes the label list. + * @param formValues The updated form values returned from the edit modal. + * @param selectedLabel The selected label row to update. + */ + editLicenseLabel(formValues: any, selectedLabel: ClarinLicenseLabel) { + const successMsg = 'clarin.license.label.edit.success'; + const errorMsg = 'clarin.license.label.edit.error'; + if (isNull(formValues) || isNull(selectedLabel)) { + this.notifyOperationStatus(null, successMsg, errorMsg); + return; + } + + const updatedLabel = new ClarinLicenseLabel(); + updatedLabel.id = selectedLabel.id; + updatedLabel._links = selectedLabel._links; + updatedLabel.type = selectedLabel.type; + updatedLabel.label = formValues.label; + updatedLabel.title = formValues.title; + updatedLabel.extended = !!formValues.extended; + + // file input: convert if a new file was selected, otherwise keep existing icon + const reader = new FileReader(); + try { + reader.readAsArrayBuffer(formValues.icon?.[0]); + reader.onerror = () => { + this.notifyOperationStatus(null, successMsg, errorMsg); + }; + reader.onloadend = (evt) => { + if (evt.target.readyState === FileReader.DONE) { + const buf = evt.target.result; + const bytes: number[] = []; + if (buf instanceof ArrayBuffer) { + const arr = new Uint8Array(buf); + for (const b of arr) { bytes.push(b); } + } + updatedLabel.icon = bytes; + this.doUpdateLabel(updatedLabel, successMsg, errorMsg); + } + }; + } catch { + // no new file selected – keep the existing icon from the stored label + updatedLabel.icon = selectedLabel.icon; + this.doUpdateLabel(updatedLabel, successMsg, errorMsg); + } + } + + /** + * Execute the actual PUT request for a label and handle notifications + dependent list refreshes. + */ + private doUpdateLabel(label: ClarinLicenseLabel, successMsg: string, errorMsg: string) { + this.clarinLicenseLabelService.put(label) + .pipe(getFirstCompletedRemoteData(), takeUntil(this.ngUnsubscribe)) + .subscribe((res: RemoteData) => { + this.notifyOperationStatus(res, successMsg, errorMsg); + if (res?.hasSucceeded) { + this.refreshLabels(); + this.loadAllLicenses(); + } + }); + } + + /** + * Ask for confirmation and delete the selected license label. + */ + confirmDeleteLabel(labelToDelete: ClarinLicenseLabel) { + if (isNull(labelToDelete?.id)) { + return; + } + + const labelDeleteDSO = new DSpaceObject(); + labelDeleteDSO.name = labelToDelete.label; + + const modalRef = this.modalService.open(ConfirmationModalComponent); + modalRef.componentInstance.dso = labelDeleteDSO; + modalRef.componentInstance.headerLabel = 'clarin.license.label.delete.confirm.title'; + modalRef.componentInstance.infoLabel = 'clarin.license.label.delete.confirm.message'; + modalRef.componentInstance.cancelLabel = 'clarin.license.label.delete.cancel.button'; + modalRef.componentInstance.confirmLabel = 'clarin.license.label.delete.confirm.button'; + modalRef.componentInstance.brandColor = 'danger'; + modalRef.componentInstance.confirmIcon = 'fas fa-trash'; + + modalRef.componentInstance.response + .pipe(take(1), takeUntil(this.ngUnsubscribe)) + .subscribe((confirm: boolean) => { + if (!confirm) { + return; + } + + this.clarinLicenseLabelService.delete(String(labelToDelete.id)) + .pipe(getFirstCompletedRemoteData(), takeUntil(this.ngUnsubscribe)) + .subscribe((deleteLabelResponse) => { + if (deleteLabelResponse?.hasSucceeded) { + this.notificationService.success('', this.translateService.get('clarin.license.label.delete.success')); + this.refreshLabels(); + this.loadAllLicenses(); + } else { + this.notificationService.error('', this.translateService.get('clarin.license.label.delete.error')); + } + }, () => { + this.notificationService.error('', this.translateService.get('clarin.license.label.delete.error')); + }); + }); + } + + /** + * Reload labels table using current pagination options. + */ + refreshLabels() { + this.labelsRefresh$.next(undefined); + } + /** * Pop up the notification about the request success. Messages are loaded from the `en.json5`. * @param operationResponse current response @@ -329,12 +509,16 @@ export class ClarinLicenseTableComponent implements OnInit { const searchTerm$ = new BehaviorSubject(this.searchingLicenseName); observableCombineLatest([currentPagination$, currentSort$, searchTerm$]).pipe( - scan((prevState, [currentPagination, currentSort, searchTerm]) => { + scan((prevState: { + searchTerm: string; + currentPage: number; + currentPagination: PaginationComponentOptions; + currentSort: SortOptions; + }, [currentPagination, currentSort, searchTerm]) => { // If search term has changed, reset to page 1; otherwise, keep current page const currentPage = prevState.searchTerm !== searchTerm ? 1 : currentPagination.currentPage; return { currentPage, currentPagination, currentSort, searchTerm }; - }, { searchTerm: '', currentPage: 1, currentPagination: this.getCurrentPagination(), - currentSort: this.getCurrentSort() }), + }, this.defaultListState), switchMap(({ currentPage, currentPagination, currentSort, searchTerm }) => { return this.clarinLicenseService.searchBy('byNameLike', { @@ -388,4 +572,40 @@ export class ClarinLicenseTableComponent implements OnInit { private getCurrentSort() { return this.paginationService.getCurrentSort(this.options.id, defaultSortConfiguration); } + + /** + * Initialize labels data stream so pagination query-param changes trigger fetches reactively. + */ + private initializeLabelsPaginationStream() { + const labelsLoadErrorKey = 'clarin.license.label.load.error'; + const currentLabelPagination$ = this.paginationService + .getCurrentPagination(this.labelPaginationOptions.id, this.labelPaginationOptions); + + observableCombineLatest([currentLabelPagination$, this.labelsRefresh$]) + .pipe( + switchMap(([currentPagination]) => { + this.labelsRD$.next(null); + this.loading$.next(true); + return this.clarinLicenseLabelService.findAll({ + currentPage: currentPagination.currentPage, + elementsPerPage: currentPagination.pageSize + }, false).pipe( + getFirstCompletedRemoteData() + ); + }), + takeUntil(this.ngUnsubscribe) + ) + .subscribe((labelsResponse: RemoteData>) => { + this.labelsRD$.next(labelsResponse); + if (!labelsResponse?.hasSucceeded) { + this.notificationService.error('', this.translateService.get(labelsLoadErrorKey)); + } + this.loading$.next(false); + }, () => { + this.labelsRD$.next(null); + this.notificationService.error('', this.translateService.get(labelsLoadErrorKey)); + this.loading$.next(false); + } + ); + } } diff --git a/src/app/clarin-licenses/clarin-license-table/modal/define-license-label-form/define-license-label-form.component.html b/src/app/clarin-licenses/clarin-license-table/modal/define-license-label-form/define-license-label-form.component.html index a78a285f638..2e9e4c5eb1d 100644 --- a/src/app/clarin-licenses/clarin-license-table/modal/define-license-label-form/define-license-label-form.component.html +++ b/src/app/clarin-licenses/clarin-license-table/modal/define-license-label-form/define-license-label-form.component.html @@ -2,38 +2,43 @@