Skip to content

Commit

Permalink
71712: confirmation modal for export + tests &
Browse files Browse the repository at this point in the history
- request causing error because of issue DSpace#756, commented out for now &
- drop event prevention in a HostListener like dragover event
  • Loading branch information
MarieVerdonck committed Aug 3, 2020
1 parent 66cdf9d commit 3e0f4a5
Show file tree
Hide file tree
Showing 9 changed files with 228 additions and 14 deletions.
4 changes: 2 additions & 2 deletions src/app/+admin/admin-sidebar/admin-sidebar.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {

observableCombineLatest(
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
this.scriptDataService.scriptWithNameExistsAndCanExecute(METADATA_EXPORT_SCRIPT_NAME)
// this.scriptDataService.scriptWithNameExistsAndCanExecute(METADATA_EXPORT_SCRIPT_NAME)
).pipe(
// TODO uncomment when #635 (https://github.com/DSpace/dspace-angular/issues/635) is fixed; otherwise even in production mode, the metadata export button is only available after a refresh (and not in dev mode)
// filter(([authorized, metadataExportScriptExists]: boolean[]) => authorized && metadataExportScriptExists),
Expand Down Expand Up @@ -416,7 +416,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {

observableCombineLatest(
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
this.scriptDataService.scriptWithNameExistsAndCanExecute(METADATA_IMPORT_SCRIPT_NAME)
// this.scriptDataService.scriptWithNameExistsAndCanExecute(METADATA_IMPORT_SCRIPT_NAME)
).pipe(
// TODO uncomment when #635 (https://github.com/DSpace/dspace-angular/issues/635) is fixed
// filter(([authorized, metadataImportScriptExists]: boolean[]) => authorized && metadataImportScriptExists),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<div>
<div class="modal-header">{{ headerLabel | translate:{dsoName: dso?.name} }}
<button type="button" class="close" (click)="close()" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p>{{ infoLabel | translate:{dsoName: dso?.name} }}</p>
<button type="button" class="cancel btn btn-secondary" (click)="cancelPressed()" aria-label="Cancel">
{{ cancelLabel | translate:{dsoName: dso?.name} }}
</button>
<button type="button" class="confirm btn btn-primary" (click)="confirmPressed()" aria-label="Confirm" ngbAutofocus>
{{ confirmLabel | translate:{dsoName: dso?.name} }}
</button>
</div>
</div>
117 changes: 117 additions & 0 deletions src/app/shared/confirmation-modal/confirmation-modal.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core';
import { ConfirmationModalComponent } from './confirmation-modal.component';

describe('ConfirmationModalComponent', () => {
let component: ConfirmationModalComponent;
let fixture: ComponentFixture<ConfirmationModalComponent>;
let debugElement: DebugElement;

const modalStub = jasmine.createSpyObj('modalStub', ['close']);

beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
declarations: [ConfirmationModalComponent],
providers: [
{ provide: NgbActiveModal, useValue: modalStub }
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();

}));

beforeEach(() => {
fixture = TestBed.createComponent(ConfirmationModalComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});

describe('close', () => {
beforeEach(() => {
component.close();
});
it('should call the close method on the active modal', () => {
expect(modalStub.close).toHaveBeenCalled();
});
});

describe('confirmPressed', () => {
beforeEach(() => {
spyOn(component.response, 'emit');
component.confirmPressed();
});
it('should call the close method on the active modal', () => {
expect(modalStub.close).toHaveBeenCalled();
});
it('event emitter should emit true', () => {
expect(component.response.emit).toHaveBeenCalledWith(true);
});
});

describe('cancelPressed', () => {
beforeEach(() => {
spyOn(component.response, 'emit');
component.cancelPressed();
});
it('should call the close method on the active modal', () => {
expect(modalStub.close).toHaveBeenCalled();
});
it('event emitter should emit false', () => {
expect(component.response.emit).toHaveBeenCalledWith(false);
});
});

describe('when the click method emits on close button', () => {
beforeEach(fakeAsync(() => {
spyOn(component, 'close');
debugElement.query(By.css('button.close')).triggerEventHandler('click', {});
tick();
fixture.detectChanges();
}));
it('should call the close method on the component', () => {
expect(component.close).toHaveBeenCalled();
});
});

describe('when the click method emits on cancel button', () => {
beforeEach(fakeAsync(() => {
spyOn(component, 'close');
spyOn(component.response, 'emit');
debugElement.query(By.css('button.cancel')).triggerEventHandler('click', {});
tick();
fixture.detectChanges();
}));
it('should call the close method on the component', () => {
expect(component.close).toHaveBeenCalled();
});
it('event emitter should emit false', () => {
expect(component.response.emit).toHaveBeenCalledWith(false);
});
});

describe('when the click method emits on confirm button', () => {
beforeEach(fakeAsync(() => {
spyOn(component, 'close');
spyOn(component.response, 'emit');
debugElement.query(By.css('button.confirm')).triggerEventHandler('click', {});
tick();
fixture.detectChanges();
}));
it('should call the close method on the component', () => {
expect(component.close).toHaveBeenCalled();
});
it('event emitter should emit true', () => {
expect(component.response.emit).toHaveBeenCalledWith(true);
});
});

});
48 changes: 48 additions & 0 deletions src/app/shared/confirmation-modal/confirmation-modal.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { DSpaceObject } from '../../core/shared/dspace-object.model';

@Component({
selector: 'ds-confirmation-modal',
templateUrl: 'confirmation-modal.component.html',
})
export class ConfirmationModalComponent {
@Input() headerLabel: string;
@Input() infoLabel: string;
@Input() cancelLabel: string;
@Input() confirmLabel: string;
@Input() dso: DSpaceObject;

/**
* An event fired when the cancel or confirm button is clicked, with respectively false or true
*/
@Output()
response = new EventEmitter<boolean>();

constructor(protected activeModal: NgbActiveModal) {
}

/**
* Confirm the action that led to the modal
*/
confirmPressed() {
this.response.emit(true);
this.close();
}

/**
* Cancel the action that led to the modal and close modal
*/
cancelPressed() {
this.response.emit(false);
this.close();
}

/**
* Close the modal
*/
close() {
this.activeModal.close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,27 @@ import { TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs/internal/Observable';
import { take, map } from 'rxjs/operators';
import { of as observableOf } from 'rxjs';
import { AuthService } from '../../../../core/auth/auth.service';
import { METADATA_EXPORT_SCRIPT_NAME, ScriptDataService } from '../../../../core/data/processes/script-data.service';
import { RequestEntry } from '../../../../core/data/request.reducer';
import { Collection } from '../../../../core/shared/collection.model';
import { Community } from '../../../../core/shared/community.model';
import { DSpaceObjectType } from '../../../../core/shared/dspace-object-type.model';
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { ProcessParameter } from '../../../../process-page/processes/process-parameter.model';
import { ConfirmationModalComponent } from '../../../confirmation-modal/confirmation-modal.component';
import { isNotEmpty } from '../../../empty.util';
import { NotificationsService } from '../../../notifications/notifications.service';
import { createSuccessfulRemoteDataObject } from '../../../remote-data.utils';
import { DSOSelectorModalWrapperComponent, SelectorActionType } from '../dso-selector-modal-wrapper.component';

/**
* Component to wrap a list of existing dso's inside a modal
* Used to choose a dso from to export metadata of
*/
@Component({
selector: 'ds-edit-item-selector',
selector: 'ds-export-metadata-selector',
templateUrl: '../dso-selector-modal-wrapper.component.html',
})
export class ExportMetadataSelectorComponent extends DSOSelectorModalWrapperComponent implements OnInit {
Expand All @@ -31,7 +34,8 @@ export class ExportMetadataSelectorComponent extends DSOSelectorModalWrapperComp

constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router,
protected notificationsService: NotificationsService, protected translationService: TranslateService,
protected scriptDataService: ScriptDataService) {
protected scriptDataService: ScriptDataService,
private modalService: NgbModal) {
super(activeModal, route);
}

Expand All @@ -41,9 +45,23 @@ export class ExportMetadataSelectorComponent extends DSOSelectorModalWrapperComp
*/
navigate(dso: DSpaceObject): Observable<boolean> {
if (dso instanceof Collection || dso instanceof Community) {
const startScriptSucceeded = this.startScriptNotifyAndRedirect(dso, dso.handle);
startScriptSucceeded.pipe(take(1)).subscribe();
return startScriptSucceeded;
const modalRef = this.modalService.open(ConfirmationModalComponent);
modalRef.componentInstance.dso = dso;
modalRef.componentInstance.headerLabel = "confirmation-modal.export-metadata.header";
modalRef.componentInstance.infoLabel = "confirmation-modal.export-metadata.info";
modalRef.componentInstance.cancelLabel = "confirmation-modal.export-metadata.cancel";
modalRef.componentInstance.confirmLabel = "confirmation-modal.export-metadata.confirm";

modalRef.componentInstance.response.subscribe((confirm: boolean) => {
if (confirm) {
const startScriptSucceeded = this.startScriptNotifyAndRedirect(dso, dso.handle);
startScriptSucceeded.pipe(take(1)).subscribe();
return startScriptSucceeded;
} else {
const modalRef = this.modalService.open(ExportMetadataSelectorComponent);
modalRef.componentInstance.dsoRD = createSuccessfulRemoteDataObject(dso);
}
});
} else {
this.notificationsService.error(this.translationService.get('dso-selector.export-metadata.notValidDSO'));
return observableOf(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,17 @@ export class FileDropzoneNoUploaderComponent implements OnInit {
ngOnInit() {
this.uploaderId = 'ds-drag-and-drop-uploader' + uniqueId();
this.isOverDocumentDropZone = observableOf(false);
window.addEventListener('drop', (e: DragEvent) => {
return e && e.preventDefault();
}, false);
this.uploader = new FileUploader({
// required, but using onFileDrop, not uploader
url: 'placeholder',
});
}

@HostListener('window:drop', ['$event'])
onDrop(event: any) {
event.preventDefault();
}

@HostListener('window:dragover', ['$event'])
onDragOver(event: any) {
// Show drop area on the page
Expand Down
7 changes: 5 additions & 2 deletions src/app/shared/shared.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { MissingTranslationHandler, TranslateModule } from '@ngx-translate/core'

import { NgxPaginationModule } from 'ngx-pagination';
import { ComcolRoleComponent } from './comcol-forms/edit-comcol-page/comcol-role/comcol-role.component';
import { ConfirmationModalComponent } from './confirmation-modal/confirmation-modal.component';
import { ExportMetadataSelectorComponent } from './dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component';
import { FileDropzoneNoUploaderComponent } from './file-dropzone-no-uploader/file-dropzone-no-uploader.component';
import { PublicationListElementComponent } from './object-list/item-list-element/item-types/publication/publication-list-element.component';
Expand Down Expand Up @@ -401,7 +402,8 @@ const COMPONENTS = [
GroupSearchBoxComponent,
FileDownloadLinkComponent,
CollectionDropdownComponent,
ExportMetadataSelectorComponent
ExportMetadataSelectorComponent,
ConfirmationModalComponent
];

const ENTRY_COMPONENTS = [
Expand Down Expand Up @@ -478,7 +480,8 @@ const ENTRY_COMPONENTS = [
ClaimedTaskActionsEditMetadataComponent,
FileDownloadLinkComponent,
CurationFormComponent,
ExportMetadataSelectorComponent
ExportMetadataSelectorComponent,
ConfirmationModalComponent
];

const SHARED_ITEM_PAGE_COMPONENTS = [
Expand Down
10 changes: 10 additions & 0 deletions src/assets/i18n/en.json5
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,16 @@



"confirmation-modal.export-metadata.header": "Export metadata for {{ dsoName }}",

"confirmation-modal.export-metadata.info": "Are you sure you want to export metadata for {{ dsoName }}",

"confirmation-modal.export-metadata.cancel": "Cancel",

"confirmation-modal.export-metadata.confirm": "Export",



"error.bitstream": "Error fetching bitstream",

"error.browse-by": "Error fetching items",
Expand Down
2 changes: 1 addition & 1 deletion src/environments/mock-environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export const environment: Partial<GlobalConfig> = {
},
// Angular Universal settings
universal: {
preboot: true,
preboot: false,
async: true,
time: false
},
Expand Down

0 comments on commit 3e0f4a5

Please sign in to comment.