mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
DSC-1111 Working uploader, cache refresh and retrieve for the updated dso containing the logo, new confirmationmodal
This commit is contained in:
@@ -11,11 +11,7 @@
|
||||
</div>
|
||||
<div class="col-4 d-inline-block">
|
||||
<div *ngIf="logo" class="float-right">
|
||||
<button (click)="confirmLogoDelete(removeLogo)" class="btn btn-danger"
|
||||
title="{{type.value + '.edit.logo.delete.title' | translate}}" type="button">
|
||||
<i aria-hidden="true" class="fas fa-trash"></i>
|
||||
{{ 'community.edit.logo.delete.title' | translate}}
|
||||
</button>
|
||||
<button (click)="confirmLogoDeleteWithModal()" data-test="open-delete-modal-button" class="btn btn-danger" type="button">{{'community.edit.logo.delete.title' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -42,17 +38,4 @@
|
||||
<i class="fas fa-arrow-left" aria-hidden="true"></i> {{ type.value + '.edit.return' | translate }}
|
||||
</button>
|
||||
</ds-form>
|
||||
<ng-template #removeLogo let-c="close" let-d="dismiss">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title text-info">{{'collection.edit.logo.delete.title' | translate}}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{'admin.edit-user-agreement.confirm.info' | translate}}</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button (click)="c('delete')" class="btn btn-danger"
|
||||
type="button">{{'collection.edit.logo.delete.title' | translate}}</button>
|
||||
<button (click)="c('cancel')" class="btn btn-warning" type="button">{{'form.cancel' | translate}}</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
|
@@ -19,7 +19,7 @@ import { NotificationsServiceStub } from '../../../testing/notifications-service
|
||||
import { VarDirective } from '../../../utils/var.directive';
|
||||
import { ComColFormComponent } from './comcol-form.component';
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../../remote-data.utils';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../../remote-data.utils';
|
||||
|
||||
describe('ComColFormComponent', () => {
|
||||
let comp: ComColFormComponent<any>;
|
||||
@@ -61,7 +61,8 @@ describe('ComColFormComponent', () => {
|
||||
const logoEndpoint = 'rest/api/logo/endpoint';
|
||||
const dsoService = Object.assign({
|
||||
getLogoEndpoint: () => observableOf(logoEndpoint),
|
||||
deleteLogo: () => createSuccessfulRemoteDataObject$({})
|
||||
deleteLogo: () => createSuccessfulRemoteDataObject$({}),
|
||||
findById: () => createSuccessfulRemoteDataObject$({})
|
||||
});
|
||||
const notificationsService = new NotificationsServiceStub();
|
||||
|
||||
@@ -70,7 +71,8 @@ describe('ComColFormComponent', () => {
|
||||
/* eslint-enable no-empty, @typescript-eslint/no-empty-function */
|
||||
|
||||
const requestServiceStub = jasmine.createSpyObj('requestService', {
|
||||
removeByHrefSubstring: {}
|
||||
removeByHrefSubstring: {},
|
||||
setStaleByHrefSubstring: {}
|
||||
});
|
||||
const objectCacheStub = jasmine.createSpyObj('objectCache', {
|
||||
remove: {}
|
||||
@@ -148,8 +150,6 @@ describe('ComColFormComponent', () => {
|
||||
type: Community.type,
|
||||
}
|
||||
),
|
||||
uploader: undefined,
|
||||
deleteLogo: false,
|
||||
operations: operations,
|
||||
}
|
||||
);
|
||||
@@ -158,32 +158,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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -204,6 +194,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', () => {
|
||||
@@ -222,96 +217,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);
|
||||
});
|
||||
|
||||
describe('submit with logo marked for deletion', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(dsoService, 'deleteLogo').and.callThrough();
|
||||
comp.markLogoForDeletion = true;
|
||||
});
|
||||
|
||||
it('should call dsoService.deleteLogo on the DSO', () => {
|
||||
comp.onSubmit();
|
||||
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 display a success notification', () => {
|
||||
expect(notificationsService.success).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when dsoService.deleteLogo returns an error response', () => {
|
||||
beforeEach(() => {
|
||||
dsoService.deleteLogo.and.returnValue(createFailedRemoteDataObject$('Error', 500));
|
||||
comp.onSubmit();
|
||||
});
|
||||
|
||||
it('should display an error notification', () => {
|
||||
expect(notificationsService.error).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteLogo', () => {
|
||||
beforeEach(() => {
|
||||
comp.deleteLogo();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should set markLogoForDeletion to true', () => {
|
||||
expect(comp.markLogoForDeletion).toEqual(true);
|
||||
});
|
||||
|
||||
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 hide the delete button', () => {
|
||||
const button = fixture.debugElement.query(By.css('#logo-section .btn-danger'));
|
||||
expect(button).not.toBeTruthy();
|
||||
});
|
||||
|
||||
it('should show the undo button', () => {
|
||||
const button = fixture.debugElement.query(By.css('#logo-section .btn-warning'));
|
||||
expect(button).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('undoDeleteLogo', () => {
|
||||
beforeEach(() => {
|
||||
comp.markLogoForDeletion = true;
|
||||
comp.undoDeleteLogo();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should set markLogoForDeletion to false', () => {
|
||||
expect(comp.markLogoForDeletion).toEqual(false);
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
it('should show the delete button', () => {
|
||||
it('should show the delete logo button', () => {
|
||||
const button = fixture.debugElement.query(By.css('#logo-section .btn-danger'));
|
||||
expect(button).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should hide the undo button', () => {
|
||||
const button = fixture.debugElement.query(By.css('#logo-section .btn-warning'));
|
||||
expect(button).not.toBeTruthy();
|
||||
describe('when the delete logo button is clicked', () => {
|
||||
beforeEach(() => {
|
||||
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();
|
||||
});
|
||||
|
||||
it('should create a confirmation modal with the correct labels and properties', () => {
|
||||
const modalServiceSpy = spyOn((comp as any).modalService, 'open').and.callThrough();
|
||||
|
||||
const modalRef = comp.createConfirmationModal();
|
||||
|
||||
expect(modalServiceSpy).toHaveBeenCalled();
|
||||
|
||||
expect(modalRef).toBeDefined();
|
||||
expect(modalRef.componentInstance).toBeDefined();
|
||||
|
||||
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 call createConfirmationModal method', () => {
|
||||
expect(comp.createConfirmationModal).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call subscribeToConfirmationResponse method', () => {
|
||||
expect(comp.subscribeToConfirmationResponse).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('when the modal is closed', () => {
|
||||
|
||||
let modalRef;
|
||||
|
||||
beforeEach(() => {
|
||||
modalRef = comp.createConfirmationModal();
|
||||
comp.subscribeToConfirmationResponse(modalRef);
|
||||
});
|
||||
|
||||
it('should call handleLogoDeletion and dsoService.deleteLogo methods when deletion is confirmed', waitForAsync(() => {
|
||||
modalRef.componentInstance.confirmPressed();
|
||||
|
||||
expect(comp.handleLogoDeletion).toHaveBeenCalled();
|
||||
expect(dsoService.deleteLogo).toHaveBeenCalled();
|
||||
|
||||
}));
|
||||
|
||||
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();
|
||||
}));
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -3,13 +3,12 @@ import { UntypedFormGroup } from '@angular/forms';
|
||||
import { DynamicFormControlModel, DynamicFormService, DynamicInputModel } from '@ng-dynamic-forms/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { FileUploader } from 'ng2-file-upload';
|
||||
import { BehaviorSubject, combineLatest as observableCombineLatest, Subscription } from 'rxjs';
|
||||
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs';
|
||||
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';
|
||||
@@ -22,8 +21,11 @@ import { UploaderComponent } from '../../../upload/uploader/uploader.component';
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import { NoContent } from '../../../../core/shared/NoContent.model';
|
||||
import { getFirstCompletedRemoteData } from '../../../../core/shared/operators';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { followLink } from '../../../utils/follow-link-config.model';
|
||||
import { ConfirmationModalComponent } from '../../../confirmation-modal/confirmation-modal.component';
|
||||
import { map, take, tap } from 'rxjs/operators';
|
||||
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
|
||||
|
||||
/**
|
||||
* A form for creating and editing Communities or Collections
|
||||
@@ -92,12 +94,7 @@ export class ComColFormComponent<T extends Collection | Community> implements On
|
||||
@Output() back: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
/**
|
||||
* Fires an event when the logo has finished uploading (with or without errors) or was removed
|
||||
*/
|
||||
@Output() finish: EventEmitter<any> = 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);
|
||||
@@ -147,10 +144,6 @@ export class ComColFormComponent<T extends Collection | Community> implements On
|
||||
]).subscribe(([href, logoRD]: [string, RemoteData<Bitstream>]) => {
|
||||
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);
|
||||
})
|
||||
);
|
||||
@@ -224,73 +217,129 @@ export class ComColFormComponent<T extends Collection | Community> implements On
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that provides a modal
|
||||
* Helper method that confirms the deletion of the logo opening a confirmation modal
|
||||
*/
|
||||
openModal(content: any) {
|
||||
this.modalService.open(content);
|
||||
confirmLogoDeleteWithModal(): void {
|
||||
const modalRef = this.createConfirmationModal();
|
||||
this.subscribeToConfirmationResponse(modalRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that confirms the deletion of the logo and handles possible errors
|
||||
* Creates and opens the confirmation modal
|
||||
* @returns Reference to the opened modal
|
||||
*/
|
||||
confirmLogoDelete(removeLogo: any) {
|
||||
//this.refreshCache()
|
||||
this.modalService.open(removeLogo).result.then((result) => {
|
||||
if (result === 'delete') {
|
||||
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';
|
||||
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<NoContent>) => {
|
||||
const successMessageKey = `${this.type.value}.edit.logo.notifications.delete.success`;
|
||||
const errorMessageKey = `${this.type.value}.edit.logo.notifications.delete.error`;
|
||||
|
||||
if (response.hasSucceeded) {
|
||||
this.refreshCache();
|
||||
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')
|
||||
);
|
||||
this.handleSuccessfulDeletion(successMessageKey);
|
||||
} else {
|
||||
this.notificationsService.error(
|
||||
this.translate.get(this.type.value + '.edit.logo.notifications.delete.error.title'),
|
||||
response.errorMessage
|
||||
);
|
||||
this.handleFailedDeletion(errorMessageKey, response.errorMessage);
|
||||
}
|
||||
this.dso.logo = undefined;
|
||||
this.uploadFilesOptions.method = RestRequestMethod.POST;
|
||||
// this.finish.emit();
|
||||
|
||||
});
|
||||
|
||||
} else if (result === 'cancel') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles successful logo deletion
|
||||
* @param successMessageKey Translation key for success message
|
||||
*/
|
||||
private handleSuccessfulDeletion(successMessageKey: string): void {
|
||||
this.refreshDsoCache();
|
||||
this.notificationsService.success(
|
||||
this.translate.get(`${successMessageKey}.title`),
|
||||
this.translate.get(`${successMessageKey}.content`)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the object's cache to ensure the latest version
|
||||
* Handles failed logo deletion
|
||||
* @param errorMessageKey Translation key for error message
|
||||
* @param errorMessage Error message from the response
|
||||
*/
|
||||
private refreshCache() {
|
||||
private handleFailedDeletion(errorMessageKey: string, errorMessage: string): void {
|
||||
this.notificationsService.error(
|
||||
this.translate.get(`${errorMessageKey}.title`),
|
||||
errorMessage
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the object's cache to obtain the latest version
|
||||
*/
|
||||
private refreshDsoCache() {
|
||||
this.clearDsoCache();
|
||||
return this.fetchUpdatedDso();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the cache related to the current dso
|
||||
*/
|
||||
private clearDsoCache() {
|
||||
this.requestService.setStaleByHrefSubstring(this.dso.id);
|
||||
this.objectCache.remove(this.dso._links.self.href);
|
||||
this.dsoService.findById(this.dso.id, false, true, followLink('logo')).pipe(
|
||||
getFirstCompletedRemoteData()
|
||||
).subscribe((rd: RemoteData<T>) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the latest data for the dso
|
||||
*/
|
||||
private fetchUpdatedDso(): Observable<DSpaceObject | null> {
|
||||
return this.dsoService.findById(this.dso.id, false, true, followLink('logo')).pipe(
|
||||
tap((rd: RemoteData<T>) => {
|
||||
if (rd.hasSucceeded) {
|
||||
this.dso = rd.payload;
|
||||
}
|
||||
});
|
||||
}),
|
||||
map((rd: RemoteData<T>) => rd.hasSucceeded ? rd.payload : null)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The request was successful, display a success notification
|
||||
*/
|
||||
public onCompleteItem() {
|
||||
if (hasValue(this.dso.id)) {
|
||||
this.refreshCache();
|
||||
this.refreshDsoCache();
|
||||
}
|
||||
this.notificationsService.success(null, this.translate.get(this.type.value + '.edit.logo.notifications.add.success'));
|
||||
// this.finish.emit();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -298,7 +347,6 @@ export class ComColFormComponent<T extends Collection | Community> implements On
|
||||
*/
|
||||
public onUploadError() {
|
||||
this.notificationsService.error(null, this.translate.get(this.type.value + '.edit.logo.notifications.add.error'));
|
||||
this.finish.emit();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1228,8 +1228,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.",
|
||||
@@ -1586,6 +1590,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",
|
||||
|
Reference in New Issue
Block a user