diff --git a/src/app/collection-page/collection-form/collection-form.component.ts b/src/app/collection-page/collection-form/collection-form.component.ts index fd657173444..0758435db88 100644 --- a/src/app/collection-page/collection-form/collection-form.component.ts +++ b/src/app/collection-page/collection-form/collection-form.component.ts @@ -12,6 +12,7 @@ import { SimpleChange, SimpleChanges, } from '@angular/core'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { DynamicFormControlModel, DynamicFormOptionConfig, @@ -30,7 +31,7 @@ import { import { AuthService } from '../../core/auth/auth.service'; import { ObjectCacheService } from '../../core/cache/object-cache.service'; -import { CommunityDataService } from '../../core/data/community-data.service'; +import { CollectionDataService } from '../../core/data/collection-data.service'; import { EntityTypeDataService } from '../../core/data/entity-type-data.service'; import { RequestService } from '../../core/data/request.service'; import { Collection } from '../../core/shared/collection.model'; @@ -95,12 +96,13 @@ export class CollectionFormComponent extends ComColFormComponent imp protected translate: TranslateService, protected notificationsService: NotificationsService, protected authService: AuthService, - protected dsoService: CommunityDataService, + protected dsoService: CollectionDataService, protected requestService: RequestService, protected objectCache: ObjectCacheService, protected entityTypeService: EntityTypeDataService, - protected chd: ChangeDetectorRef) { - super(formService, translate, notificationsService, authService, requestService, objectCache); + protected chd: ChangeDetectorRef, + protected modalService: NgbModal) { + super(formService, translate, notificationsService, authService, requestService, objectCache, modalService); } ngOnInit(): void { diff --git a/src/app/collection-page/create-collection-page/create-collection-page.component.html b/src/app/collection-page/create-collection-page/create-collection-page.component.html index 0907290ac38..6ca55339247 100644 --- a/src/app/collection-page/create-collection-page/create-collection-page.component.html +++ b/src/app/collection-page/create-collection-page/create-collection-page.component.html @@ -6,8 +6,8 @@ + [isCreation]="true" + (back)="navigateToHome()">
diff --git a/src/app/collection-page/edit-collection-page/collection-metadata/collection-metadata.component.html b/src/app/collection-page/edit-collection-page/collection-metadata/collection-metadata.component.html index 1cf40159ec9..845c82458a5 100644 --- a/src/app/collection-page/edit-collection-page/collection-metadata/collection-metadata.component.html +++ b/src/app/collection-page/edit-collection-page/collection-metadata/collection-metadata.component.html @@ -17,6 +17,7 @@
diff --git a/src/app/community-page/community-form/community-form.component.ts b/src/app/community-page/community-form/community-form.component.ts index e9c5f9bf292..d32d9e408f2 100644 --- a/src/app/community-page/community-form/community-form.component.ts +++ b/src/app/community-page/community-form/community-form.component.ts @@ -10,6 +10,7 @@ import { SimpleChange, SimpleChanges, } from '@angular/core'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { DynamicFormControlModel, DynamicFormService, @@ -108,8 +109,9 @@ export class CommunityFormComponent extends ComColFormComponent imple protected authService: AuthService, protected dsoService: CommunityDataService, protected requestService: RequestService, - protected objectCache: ObjectCacheService) { - super(formService, translate, notificationsService, authService, requestService, objectCache); + protected objectCache: ObjectCacheService, + protected modalService: NgbModal) { + super(formService, translate, notificationsService, authService, requestService, objectCache, modalService); } ngOnChanges(changes: SimpleChanges) { diff --git a/src/app/community-page/create-community-page/create-community-page.component.html b/src/app/community-page/create-community-page/create-community-page.component.html index 4c634dab8e9..219f6470b09 100644 --- a/src/app/community-page/create-community-page/create-community-page.component.html +++ b/src/app/community-page/create-community-page/create-community-page.component.html @@ -2,15 +2,15 @@
- +

{{ 'community.create.sub-head' | translate:{ parent: dsoNameService.getName(parent) } }}

+ [isCreation]="true" + (back)="navigateToHome()">
diff --git a/src/app/community-page/edit-community-page/community-metadata/community-metadata.component.html b/src/app/community-page/edit-community-page/community-metadata/community-metadata.component.html index 2ca5b768e4d..bf75944242f 100644 --- a/src/app/community-page/edit-community-page/community-metadata/community-metadata.component.html +++ b/src/app/community-page/edit-community-page/community-metadata/community-metadata.component.html @@ -1,5 +1,5 @@ - diff --git a/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.html b/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.html index c79dba42b78..1d752869e5e 100644 --- a/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.html +++ b/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.html @@ -4,22 +4,17 @@ {{type.value + '.edit.logo.label' | translate}}
-
+
-
- -
@@ -43,7 +38,8 @@ [formModel]="formModel" [displayCancel]="false" (submitForm)="onSubmit()"> - + diff --git a/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.spec.ts b/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.spec.ts index 73d00e81c3a..2035c313fe6 100644 --- a/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.spec.ts +++ b/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.spec.ts @@ -29,10 +29,7 @@ import { hasValue } from '../../../empty.util'; import { FormComponent } from '../../../form/form.component'; import { AuthServiceMock } from '../../../mocks/auth.service.mock'; import { NotificationsService } from '../../../notifications/notifications.service'; -import { - createFailedRemoteDataObject$, - createSuccessfulRemoteDataObject$, -} from '../../../remote-data.utils'; +import { createSuccessfulRemoteDataObject$ } from '../../../remote-data.utils'; import { NotificationsServiceStub } from '../../../testing/notifications-service.stub'; import { UploaderComponent } from '../../../upload/uploader/uploader.component'; import { VarDirective } from '../../../utils/var.directive'; @@ -80,6 +77,7 @@ describe('ComColFormComponent', () => { const dsoService = Object.assign({ getLogoEndpoint: () => observableOf(logoEndpoint), deleteLogo: () => createSuccessfulRemoteDataObject$({}), + findById: () => createSuccessfulRemoteDataObject$({}), }); const notificationsService = new NotificationsServiceStub(); @@ -89,6 +87,7 @@ describe('ComColFormComponent', () => { const requestServiceStub = jasmine.createSpyObj('requestService', { removeByHrefSubstring: {}, + setStaleByHrefSubstring: {}, }); const objectCacheStub = jasmine.createSpyObj('objectCache', { remove: {}, @@ -175,8 +174,6 @@ describe('ComColFormComponent', () => { type: Community.type, }, ), - uploader: undefined, - deleteLogo: false, operations: operations, }, ); @@ -185,32 +182,22 @@ describe('ComColFormComponent', () => { describe('onCompleteItem', () => { beforeEach(() => { - spyOn(comp.finish, 'emit'); comp.onCompleteItem(); }); it('should show a success notification', () => { expect(notificationsService.success).toHaveBeenCalled(); }); - - it('should emit finish', () => { - expect(comp.finish.emit).toHaveBeenCalled(); - }); }); describe('onUploadError', () => { beforeEach(() => { - spyOn(comp.finish, 'emit'); comp.onUploadError(); }); it('should show an error notification', () => { expect(notificationsService.error).toHaveBeenCalled(); }); - - it('should emit finish', () => { - expect(comp.finish.emit).toHaveBeenCalled(); - }); }); }); @@ -231,6 +218,11 @@ describe('ComColFormComponent', () => { it('should initialize the uploadFilesOptions with a POST method', () => { expect(comp.uploadFilesOptions.method).toEqual(RestRequestMethod.POST); }); + + it('should not show the delete logo button', () => { + const button = fixture.debugElement.query(By.css('#logo-section .btn-danger')); + expect(button).toBeFalsy(); + }); }); describe('and the dso contains a logo', () => { @@ -249,96 +241,71 @@ describe('ComColFormComponent', () => { expect(comp.uploadFilesOptions.url).toEqual(logoEndpoint); }); - it('should initialize the uploadFilesOptions with a PUT method', () => { - expect(comp.uploadFilesOptions.method).toEqual(RestRequestMethod.PUT); + it('should show the delete logo button', () => { + const button = fixture.debugElement.query(By.css('#logo-section .btn-danger')); + expect(button).toBeTruthy(); }); - describe('submit with logo marked for deletion', () => { + describe('when the delete logo button is clicked', () => { beforeEach(() => { - spyOn(dsoService, 'deleteLogo').and.callThrough(); - comp.markLogoForDeletion = true; - }); - - it('should call dsoService.deleteLogo on the DSO', () => { - comp.onSubmit(); + spyOn(dsoService, 'deleteLogo').and.returnValue(createSuccessfulRemoteDataObject$({})); + spyOn(comp, 'handleLogoDeletion').and.callThrough(); + spyOn(comp, 'createConfirmationModal').and.callThrough(); + spyOn(comp, 'subscribeToConfirmationResponse').and.callThrough(); + const deleteButton = fixture.debugElement.query(By.css('#logo-section .btn-danger')); + deleteButton.nativeElement.click(); fixture.detectChanges(); - - expect(dsoService.deleteLogo).toHaveBeenCalledWith(comp.dso); }); - describe('when dsoService.deleteLogo returns a successful response', () => { - beforeEach(() => { - dsoService.deleteLogo.and.returnValue(createSuccessfulRemoteDataObject$({})); - comp.onSubmit(); - }); + it('should create a confirmation modal with the correct labels and properties', () => { + const modalServiceSpy = spyOn((comp as any).modalService, 'open').and.callThrough(); - it('should display a success notification', () => { - expect(notificationsService.success).toHaveBeenCalled(); - }); - }); + const modalRef = comp.createConfirmationModal(); - describe('when dsoService.deleteLogo returns an error response', () => { - beforeEach(() => { - dsoService.deleteLogo.and.returnValue(createFailedRemoteDataObject$('Error', 500)); - comp.onSubmit(); - }); + expect(modalServiceSpy).toHaveBeenCalled(); - it('should display an error notification', () => { - expect(notificationsService.error).toHaveBeenCalled(); - }); - }); - }); + expect(modalRef).toBeDefined(); + expect(modalRef.componentInstance).toBeDefined(); - describe('deleteLogo', () => { - beforeEach(() => { - comp.deleteLogo(); - fixture.detectChanges(); + expect(modalRef.componentInstance.headerLabel).toBe('community-collection.edit.logo.delete.title'); + expect(modalRef.componentInstance.infoLabel).toBe('confirmation-modal.delete-community-collection-logo.info'); + expect(modalRef.componentInstance.cancelLabel).toBe('form.cancel'); + expect(modalRef.componentInstance.confirmLabel).toBe('community-collection.edit.logo.delete.title'); + expect(modalRef.componentInstance.confirmIcon).toBe('fas fa-trash'); }); - it('should set markLogoForDeletion to true', () => { - expect(comp.markLogoForDeletion).toEqual(true); + it('should call createConfirmationModal method', () => { + expect(comp.createConfirmationModal).toHaveBeenCalled(); }); - it('should mark the logo section with a danger alert', () => { - const logoSection = fixture.debugElement.query(By.css('#logo-section.alert-danger')); - expect(logoSection).toBeTruthy(); + it('should call subscribeToConfirmationResponse method', () => { + expect(comp.subscribeToConfirmationResponse).toHaveBeenCalled(); }); - it('should hide the delete button', () => { - const button = fixture.debugElement.query(By.css('#logo-section .btn-danger')); - expect(button).not.toBeTruthy(); - }); + describe('when the modal is closed', () => { - it('should show the undo button', () => { - const button = fixture.debugElement.query(By.css('#logo-section .btn-warning')); - expect(button).toBeTruthy(); - }); - }); + let modalRef; - describe('undoDeleteLogo', () => { - beforeEach(() => { - comp.markLogoForDeletion = true; - comp.undoDeleteLogo(); - fixture.detectChanges(); - }); + beforeEach(() => { + modalRef = comp.createConfirmationModal(); + comp.subscribeToConfirmationResponse(modalRef); + }); - it('should set markLogoForDeletion to false', () => { - expect(comp.markLogoForDeletion).toEqual(false); - }); + it('should call handleLogoDeletion and dsoService.deleteLogo methods when deletion is confirmed', waitForAsync(() => { + modalRef.componentInstance.confirmPressed(); - it('should disable the danger alert on the logo section', () => { - const logoSection = fixture.debugElement.query(By.css('#logo-section.alert-danger')); - expect(logoSection).not.toBeTruthy(); - }); + expect(comp.handleLogoDeletion).toHaveBeenCalled(); + expect(dsoService.deleteLogo).toHaveBeenCalled(); - it('should show the delete button', () => { - const button = fixture.debugElement.query(By.css('#logo-section .btn-danger')); - expect(button).toBeTruthy(); - }); + })); + + it('should not call handleLogoDeletion and dsoService.deleteLogo methods when deletion is refused', waitForAsync(() => { + modalRef.componentInstance.cancelPressed(); + + expect(comp.handleLogoDeletion).not.toHaveBeenCalled(); + expect(dsoService.deleteLogo).not.toHaveBeenCalled(); + })); - it('should hide the undo button', () => { - const button = fixture.debugElement.query(By.css('#logo-section .btn-warning')); - expect(button).not.toBeTruthy(); }); }); }); diff --git a/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.ts b/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.ts index dfca0e1ae4b..0c80ce0d1d9 100644 --- a/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.ts +++ b/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.ts @@ -13,6 +13,10 @@ import { ViewChild, } from '@angular/core'; import { UntypedFormGroup } from '@angular/forms'; +import { + NgbModal, + NgbModalRef, +} from '@ng-bootstrap/ng-bootstrap'; import { DynamicFormControlModel, DynamicFormService, @@ -27,15 +31,20 @@ import { FileUploader } from 'ng2-file-upload'; import { BehaviorSubject, combineLatest as observableCombineLatest, + Observable, Subscription, + switchMap, } from 'rxjs'; +import { + filter, + take, +} from 'rxjs/operators'; import { AuthService } from '../../../../core/auth/auth.service'; import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; import { ComColDataService } from '../../../../core/data/comcol-data.service'; import { RemoteData } from '../../../../core/data/remote-data'; import { RequestService } from '../../../../core/data/request.service'; -import { RestRequestMethod } from '../../../../core/data/rest-request-method'; import { Bitstream } from '../../../../core/shared/bitstream.model'; import { Collection } from '../../../../core/shared/collection.model'; import { Community } from '../../../../core/shared/community.model'; @@ -46,6 +55,7 @@ import { import { NoContent } from '../../../../core/shared/NoContent.model'; import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; import { ResourceType } from '../../../../core/shared/resource-type'; +import { ConfirmationModalComponent } from '../../../confirmation-modal/confirmation-modal.component'; import { hasValue, isNotEmpty, @@ -54,6 +64,7 @@ import { FormComponent } from '../../../form/form.component'; import { NotificationsService } from '../../../notifications/notifications.service'; import { UploaderComponent } from '../../../upload/uploader/uploader.component'; import { UploaderOptions } from '../../../upload/uploader/uploader-options.model'; +import { followLink } from '../../../utils/follow-link-config.model'; import { VarDirective } from '../../../utils/var.directive'; import { ComcolPageLogoComponent } from '../../comcol-page-logo/comcol-page-logo.component'; @@ -88,6 +99,11 @@ export class ComColFormComponent implements On */ @Input() dso: T; + /** + * Boolean that represents if the comcol is being created or already exists + */ + @Input() isCreation!: boolean; + /** * Type of DSpaceObject that the form represents */ @@ -126,9 +142,8 @@ export class ComColFormComponent implements On */ @Output() submitForm: EventEmitter<{ dso: T, - uploader: FileUploader, - deleteLogo: boolean, operations: Operation[], + uploader?: FileUploader }> = new EventEmitter(); /** @@ -137,21 +152,17 @@ export class ComColFormComponent implements On @Output() back: EventEmitter = new EventEmitter(); /** - * Fires an event when the logo has finished uploading (with or without errors) or was removed + * Event emitted on finish */ @Output() finish: EventEmitter = new EventEmitter(); + /** - * Observable keeping track whether or not the uploader has finished initializing + * Observable keeping track whether the uploader has finished initializing * Used to start rendering the uploader component */ initializedUploaderOptions = new BehaviorSubject(false); - /** - * Is the logo marked to be deleted? - */ - markLogoForDeletion = false; - /** * Array to track all subscriptions and unsubscribe them onDestroy * @type {Array} @@ -163,15 +174,22 @@ export class ComColFormComponent implements On */ protected dsoService: ComColDataService; + public uploader = new FileUploader(this.uploadFilesOptions); + + protected readonly refreshDSO$ = new EventEmitter(); + public constructor(protected formService: DynamicFormService, protected translate: TranslateService, protected notificationsService: NotificationsService, protected authService: AuthService, protected requestService: RequestService, - protected objectCache: ObjectCacheService) { + protected objectCache: ObjectCacheService, + protected modalService: NgbModal){ } ngOnInit(): void { + this.uploadFilesOptions.autoUpload = !this.isCreation; + if (hasValue(this.formModel)) { this.formModel.forEach( (fieldModel: DynamicInputModel) => { @@ -194,10 +212,6 @@ export class ComColFormComponent implements On ]).subscribe(([href, logoRD]: [string, RemoteData]) => { this.uploadFilesOptions.url = href; this.uploadFilesOptions.authToken = this.authService.buildAuthHeader(); - // If the object already contains a logo, send out a PUT request instead of POST for setting a new logo - if (hasValue(logoRD.payload)) { - this.uploadFilesOptions.method = RestRequestMethod.PUT; - } this.initializedUploaderOptions.next(true); }), ); @@ -208,33 +222,20 @@ export class ComColFormComponent implements On this.initializedUploaderOptions.next(true); } } + + this.subs.push( + this.refreshDSO$.pipe( + switchMap(() => this.refreshDsoCache()), + filter(rd => rd.hasSucceeded), + ).subscribe(({ payload }) => this.dso = payload), + ); + } /** * Checks which new fields were added and sends the updated version of the DSO to the parent component */ onSubmit() { - if (this.markLogoForDeletion && hasValue(this.dso.id) && hasValue(this.dso._links.logo)) { - this.dsoService.deleteLogo(this.dso).pipe( - getFirstCompletedRemoteData(), - ).subscribe((response: RemoteData) => { - if (response.hasSucceeded) { - this.notificationsService.success( - this.translate.get(this.type.value + '.edit.logo.notifications.delete.success.title'), - this.translate.get(this.type.value + '.edit.logo.notifications.delete.success.content'), - ); - } else { - this.notificationsService.error( - this.translate.get(this.type.value + '.edit.logo.notifications.delete.error.title'), - response.errorMessage, - ); - } - this.dso.logo = undefined; - this.uploadFilesOptions.method = RestRequestMethod.POST; - this.finish.emit(); - }); - } - const formMetadata = {} as MetadataMap; this.formModel.forEach((fieldModel: DynamicInputModel) => { const value: MetadataValue = { @@ -270,12 +271,18 @@ export class ComColFormComponent implements On } }); - this.submitForm.emit({ - dso: updatedDSO, - uploader: hasValue(this.uploaderComponent) ? this.uploaderComponent.uploader : undefined, - deleteLogo: this.markLogoForDeletion, - operations: operations, - }); + if (this.isCreation) { + this.submitForm.emit({ + dso: updatedDSO, + uploader: hasValue(this.uploaderComponent) ? this.uploaderComponent.uploader : undefined, + operations: operations, + }); + } else { + this.submitForm.emit({ + dso: updatedDSO, + operations: operations, + }); + } } /** @@ -296,37 +303,126 @@ export class ComColFormComponent implements On } /** - * Mark the logo to be deleted - * Send out a delete request to remove the logo from the community/collection and display notifications + * Helper method that confirms the deletion of the logo opening a confirmation modal + */ + confirmLogoDeleteWithModal(): void { + const modalRef = this.createConfirmationModal(); + this.subscribeToConfirmationResponse(modalRef); + } + + /** + * Creates and opens the confirmation modal + * @returns Reference to the opened modal + */ + createConfirmationModal(): NgbModalRef { + const modalRef = this.modalService.open(ConfirmationModalComponent); + modalRef.componentInstance.headerLabel = 'community-collection.edit.logo.delete.title'; + modalRef.componentInstance.infoLabel = 'confirmation-modal.delete-community-collection-logo.info'; + modalRef.componentInstance.cancelLabel = 'form.cancel'; + modalRef.componentInstance.confirmLabel = 'community-collection.edit.logo.delete.title'; + modalRef.componentInstance.confirmIcon = 'fas fa-trash'; + modalRef.componentInstance.brandColor = 'danger'; + return modalRef; + } + + /** + * Subscribes to the confirmation modal's response and calls the logo deletion handler if confirmed + * @param modalRef References to the opened confirmation modal + */ + subscribeToConfirmationResponse(modalRef: NgbModalRef): void { + modalRef.componentInstance.response.pipe( + take(1), + ).subscribe((confirmed: boolean) => { + if (confirmed) { + this.handleLogoDeletion(); + } + }); + } + + /** + * Method that confirms the deletion of the logo, handling both possible outcomes + */ + handleLogoDeletion(): void { + if (hasValue(this.dso.id) && hasValue(this.dso._links.logo)) { + this.dsoService.deleteLogo(this.dso).pipe( + getFirstCompletedRemoteData(), + ).subscribe((response: RemoteData) => { + const successMessageKey = `${this.type.value}.edit.logo.notifications.delete.success`; + const errorMessageKey = `${this.type.value}.edit.logo.notifications.delete.error`; + + if (response.hasSucceeded) { + this.handleSuccessfulDeletion(successMessageKey); + } else { + this.handleFailedDeletion(errorMessageKey, response.errorMessage); + } + + }); + } + } + + + /** + * Handles successful logo deletion + * @param successMessageKey Translation key for success message + */ + private handleSuccessfulDeletion(successMessageKey: string): void { + this.refreshDSO$.next(); + this.notificationsService.success( + this.translate.get(`${successMessageKey}.title`), + this.translate.get(`${successMessageKey}.content`), + ); + } + + /** + * Handles failed logo deletion + * @param errorMessageKey Translation key for error message + * @param errorMessage Error message from the response */ - deleteLogo() { - this.markLogoForDeletion = true; + private handleFailedDeletion(errorMessageKey: string, errorMessage: string): void { + this.notificationsService.error( + this.translate.get(`${errorMessageKey}.title`), + errorMessage, + ); } /** - * Undo marking the logo to be deleted + * Refresh the object's cache to obtain the latest version */ - undoDeleteLogo() { - this.markLogoForDeletion = false; + private refreshDsoCache() { + this.clearDsoCache(); + return this.fetchUpdatedDso(); } /** - * Refresh the object's cache to ensure the latest version + * Clears the cache related to the current dso */ - private refreshCache() { - this.requestService.removeByHrefSubstring(this.dso._links.self.href); + private clearDsoCache() { + this.requestService.setStaleByHrefSubstring(this.dso.id); this.objectCache.remove(this.dso._links.self.href); } + /** + * Fetches the latest data for the dso + */ + private fetchUpdatedDso(): Observable> { + return this.dsoService.findById(this.dso.id, false, true, followLink('logo')).pipe( + getFirstCompletedRemoteData(), + ) as Observable>; + } + + + /** * The request was successful, display a success notification */ public onCompleteItem() { if (hasValue(this.dso.id)) { - this.refreshCache(); + this.refreshDSO$.next(); + } + if (this.isCreation) { + this.finish.emit(); } this.notificationsService.success(null, this.translate.get(this.type.value + '.edit.logo.notifications.add.success')); - this.finish.emit(); } /** @@ -334,13 +430,13 @@ export class ComColFormComponent implements On */ public onUploadError() { this.notificationsService.error(null, this.translate.get(this.type.value + '.edit.logo.notifications.add.error')); - this.finish.emit(); } /** * Unsubscribe from open subscriptions */ ngOnDestroy(): void { + this.refreshDSO$.complete(); this.subs .filter((subscription) => hasValue(subscription)) .forEach((subscription) => subscription.unsubscribe()); diff --git a/src/app/shared/comcol/comcol-forms/create-comcol-page/create-comcol-page.component.ts b/src/app/shared/comcol/comcol-forms/create-comcol-page/create-comcol-page.component.ts index d4c311bd1be..cb2b5a9f0c5 100644 --- a/src/app/shared/comcol/comcol-forms/create-comcol-page/create-comcol-page.component.ts +++ b/src/app/shared/comcol/comcol-forms/create-comcol-page/create-comcol-page.component.ts @@ -7,12 +7,16 @@ import { TranslateService } from '@ngx-translate/core'; import { BehaviorSubject, Observable, + of, } from 'rxjs'; import { + map, mergeMap, take, + tap, } from 'rxjs/operators'; +import { getHomePageRoute } from '../../../../app-routing-paths'; import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; import { RequestParam } from '../../../../core/cache/models/request-param.model'; import { ComColDataService } from '../../../../core/data/comcol-data.service'; @@ -108,30 +112,44 @@ export class CreateComColPageComponent i return this.dsoDataService.create(dso, ...params) .pipe(getFirstSucceededRemoteDataPayload(), ); - })) - .subscribe((dsoRD: TDomain) => { - this.isLoading$.next(false); + }), + mergeMap((dsoRD: TDomain) => { if (isNotUndefined(dsoRD)) { this.newUUID = dsoRD.uuid; if (uploader.queue.length > 0) { - this.dsoDataService.getLogoEndpoint(this.newUUID).pipe(take(1)).subscribe((href: string) => { - uploader.options.url = href; - uploader.uploadAll(); - }); + return this.dsoDataService.getLogoEndpoint(this.newUUID).pipe( + take(1), + tap((href: string) => { + uploader.options.url = href; + uploader.onCompleteAll = () => { + this.isLoading$.next(false); + this.navigateToNewPage(); + this.notificationsService.success(null, this.translate.get(this.type.value + '.create.notifications.success')); + }; + uploader.uploadAll(); + }), + map(() => false), + ); } else { - this.navigateToNewPage(); + this.dsoDataService.refreshCache(dsoRD); + return of(true); } - this.dsoDataService.refreshCache(dsoRD); } + }), + ).subscribe((notify: boolean) => { + if (notify) { + this.isLoading$.next(false); + this.navigateToNewPage(); this.notificationsService.success(null, this.translate.get(this.type.value + '.create.notifications.success')); - }); + } + }); } /** * Navigate to home page */ navigateToHome() { - this.router.navigate(['/home']); + this.router.navigate([getHomePageRoute()]); } /** diff --git a/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component.spec.ts b/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component.spec.ts index e3d12d81ea6..e2b71b39414 100644 --- a/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component.spec.ts +++ b/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component.spec.ts @@ -36,8 +36,6 @@ describe('ComColMetadataComponent', () => { let routerStub; let routeStub; - const logoEndpoint = 'rest/api/logo/endpoint'; - function initializeVars() { community = Object.assign(new Community(), { uuid: 'a20da287-e174-466a-9926-f66b9300d347', @@ -58,7 +56,6 @@ describe('ComColMetadataComponent', () => { communityDataServiceStub = { update: (com, uuid?) => createSuccessfulRemoteDataObject$(newCommunity), patch: () => null, - getLogoEndpoint: () => observableOf(logoEndpoint), }; routerStub = { @@ -125,7 +122,6 @@ describe('ComColMetadataComponent', () => { }, /* eslint-enable no-empty,@typescript-eslint/no-empty-function */ }, - deleteLogo: false, }; spyOn(router, 'navigate'); }); @@ -157,48 +153,84 @@ describe('ComColMetadataComponent', () => { }); }); - describe('with at least one item in the uploader\'s queue', () => { + describe('with an empty operations array', () => { beforeEach(() => { data = { - dso: Object.assign(new Community(), { - metadata: [{ - key: 'dc.title', - value: 'test', - }], - }), + operations: [], + dso: new Community(), uploader: { options: { url: '', }, - queue: [ - {}, - ], + queue: [], /* eslint-disable no-empty,@typescript-eslint/no-empty-function */ uploadAll: () => { }, - /* eslint-enable no-empty, @typescript-eslint/no-empty-function */ + /* eslint-enable no-empty,@typescript-eslint/no-empty-function */ }, }; + spyOn(router, 'navigate'); }); - it('should not navigate', () => { - spyOn(router, 'navigate'); + it('should navigate', () => { comp.onSubmit(data); fixture.detectChanges(); - expect(router.navigate).not.toHaveBeenCalled(); + expect(router.navigate).toHaveBeenCalled(); }); + }); - it('should set the uploader\'s url to the logo\'s endpoint', () => { - comp.onSubmit(data); - fixture.detectChanges(); - expect(data.uploader.options.url).toEqual(logoEndpoint); + describe('with a not empty operations array', () => { + beforeEach(() => { + data = { + operations: [ + { + op: 'replace', + path: '/metadata/dc.title', + value: { + value: 'test', + language: null, + }, + }, + ], + dso: new Community(), + uploader: { + options: { + url: '', + }, + queue: [], + /* eslint-disable no-empty,@typescript-eslint/no-empty-function */ + uploadAll: () => { + }, + /* eslint-enable no-empty,@typescript-eslint/no-empty-function */ + }, + }; + spyOn(router, 'navigate'); }); - it('should call the uploader\'s uploadAll', () => { - spyOn(data.uploader, 'uploadAll'); - comp.onSubmit(data); - fixture.detectChanges(); - expect(data.uploader.uploadAll).toHaveBeenCalled(); + describe('when successful', () => { + + beforeEach(() => { + spyOn(dsoDataService, 'patch').and.returnValue(createSuccessfulRemoteDataObject$({})); + }); + + it('should navigate', () => { + comp.onSubmit(data); + fixture.detectChanges(); + expect(router.navigate).toHaveBeenCalled(); + }); + }); + + describe('on failure', () => { + + beforeEach(() => { + spyOn(dsoDataService, 'patch').and.returnValue(createFailedRemoteDataObject$('Error', 500)); + }); + + it('should not navigate', () => { + comp.onSubmit(data); + fixture.detectChanges(); + expect(router.navigate).not.toHaveBeenCalled(); + }); }); }); }); diff --git a/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component.ts b/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component.ts index c74153f0834..b895f6c80d9 100644 --- a/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component.ts +++ b/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component.ts @@ -23,10 +23,7 @@ import { getFirstSucceededRemoteData, } from '../../../../../core/shared/operators'; import { ResourceType } from '../../../../../core/shared/resource-type'; -import { - hasValue, - isEmpty, -} from '../../../../empty.util'; +import { isEmpty } from '../../../../empty.util'; import { NotificationsService } from '../../../../notifications/notifications.service'; @Component({ @@ -35,15 +32,14 @@ import { NotificationsService } from '../../../../notifications/notifications.se standalone: true, }) export class ComcolMetadataComponent implements OnInit { - /** - * Frontend endpoint for this type of DSO - */ - protected frontendURL: string; /** * The initial DSO object */ public dsoRD$: Observable>; - + /** + * Frontend endpoint for this type of DSO + */ + protected frontendURL: string; /** * The type of the dso */ @@ -67,36 +63,25 @@ export class ComcolMetadataComponent imp * @param event The event returned by the community/collection form. Contains the new dso and logo uploader */ onSubmit(event) { - - const uploader = event.uploader; - const deleteLogo = event.deleteLogo; - - const newLogo = hasValue(uploader) && uploader.queue.length > 0; - if (newLogo) { - this.dsoDataService.getLogoEndpoint(event.dso.uuid).pipe(take(1)).subscribe((href: string) => { - uploader.options.url = href; - uploader.uploadAll(); - }); - } - if (!isEmpty(event.operations)) { - this.dsoDataService.patch(event.dso, event.operations).pipe( - getFirstCompletedRemoteData(), - ).subscribe((response: RemoteData) => { - if (response.hasSucceeded) { - this.router.navigate([this.frontendURL + event.dso.uuid]); // todo: ok not to await this? - this.notificationsService.success(null, this.translate.get(`${this.type.value}.edit.notifications.success`)); - } else if (response.statusCode === 403) { - this.notificationsService.error(null, this.translate.get(`${this.type.value}.edit.notifications.unauthorized`)); - } else { - this.notificationsService.error(null, this.translate.get(`${this.type.value}.edit.notifications.error`)); - } - }); + this.dsoDataService.patch(event.dso, event.operations).pipe(getFirstCompletedRemoteData()) + .subscribe( (response: RemoteData) => { + if (response.hasSucceeded) { + this.router.navigate([this.frontendURL, event.dso.uuid]); // todo: ok not to await this? + this.notificationsService.success(null, this.translate.get(`${this.type.value}.edit.notifications.success`)); + } else if (response.statusCode === 403) { + this.notificationsService.error(null, this.translate.get(`${this.type.value}.edit.notifications.unauthorized`)); + } else { + this.notificationsService.error(null, this.translate.get(`${this.type.value}.edit.notifications.error`)); + } + }); + } else { + this.router.navigate([this.frontendURL, event.dso.uuid]); } } /** - * Navigate to the home page of the object + * Navigate to the relative DSO page */ navigateToHomePage() { this.dsoRD$.pipe( diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 5f313c391d7..a9529900c2d 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1432,8 +1432,12 @@ "community.edit.logo.delete.title": "Delete logo", + "community-collection.edit.logo.delete.title": "Confirm deletion", + "community.edit.logo.delete-undo.title": "Undo delete", + "community-collection.edit.logo.delete-undo.title": "Undo delete", + "community.edit.logo.label": "Community logo", "community.edit.logo.notifications.add.error": "Uploading community logo failed. Please verify the content before retrying.", @@ -1802,6 +1806,8 @@ "confirmation-modal.delete-eperson.confirm": "Delete", + "confirmation-modal.delete-community-collection-logo.info": "Are you sure you want to delete the logo?", + "confirmation-modal.delete-profile.header": "Delete Profile", "confirmation-modal.delete-profile.info": "Are you sure you want to delete your profile",