Skip to content

Commit

Permalink
Merge pull request #724 from 4Science/CST-3091
Browse files Browse the repository at this point in the history
DS-4514 start a new submission via file
  • Loading branch information
tdonohue authored Aug 6, 2020
2 parents eb98098 + 6422288 commit 4f608eb
Show file tree
Hide file tree
Showing 12 changed files with 312 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div>
<div class="modal-header">{{'dso-selector.create.submission.head' | translate}}
<button type="button" class="close" (click)="close()" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<ds-collection-dropdown (selectionChange)="selectObject($event.collection)">
</ds-collection-dropdown>
</div>
</div>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';

import { CollectionSelectorComponent } from './collection-selector.component';
import { CollectionDropdownComponent } from 'src/app/shared/collection-dropdown/collection-dropdown.component';
import { Collection } from 'src/app/core/shared/collection.model';
import { of, Observable } from 'rxjs';
import { RemoteData } from 'src/app/core/data/remote-data';
import { Community } from 'src/app/core/shared/community.model';
import { FindListOptions } from 'src/app/core/data/request.models';
import { FollowLinkConfig } from 'src/app/shared/utils/follow-link-config.model';
import { PaginatedList } from 'src/app/core/data/paginated-list';
import { createSuccessfulRemoteDataObject } from 'src/app/shared/remote-data.utils';
import { PageInfo } from 'src/app/core/shared/page-info.model';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateLoaderMock } from 'src/app/shared/mocks/translate-loader.mock';
import { CollectionDataService } from 'src/app/core/data/collection-data.service';
import { ChangeDetectorRef, ElementRef, NO_ERRORS_SCHEMA } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { ActivatedRoute } from '@angular/router';
import { hot } from 'jasmine-marbles';
import { By } from '@angular/platform-browser';

describe('CollectionSelectorComponent', () => {
let component: CollectionSelectorComponent;
let fixture: ComponentFixture<CollectionSelectorComponent>;
const modal = jasmine.createSpyObj('modal', ['close', 'dismiss']);

const community: Community = Object.assign(new Community(), {
id: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88',
uuid: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88',
name: 'Community 1'
});

const collections: Collection[] = [
Object.assign(new Collection(), {
id: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88',
name: 'Collection 1',
metadata: [
{
key: 'dc.title',
language: 'en_US',
value: 'Community 1-Collection 1'
}],
parentCommunity: of(
new RemoteData(false, false, true, undefined, community, 200)
)
}),
Object.assign(new Collection(), {
id: '59ee713b-ee53-4220-8c3f-9860dc84fe33',
name: 'Collection 2',
metadata: [
{
key: 'dc.title',
language: 'en_US',
value: 'Community 1-Collection 2'
}],
parentCommunity: of(
new RemoteData(false, false, true, undefined, community, 200)
)
}),
Object.assign(new Collection(), {
id: 'e9dbf393-7127-415f-8919-55be34a6e9ed',
name: 'Collection 3',
metadata: [
{
key: 'dc.title',
language: 'en_US',
value: 'Community 1-Collection 3'
}],
parentCommunity: of(
new RemoteData(false, false, true, undefined, community, 200)
)
}),
Object.assign(new Collection(), {
id: '59da2ff0-9bf4-45bf-88be-e35abd33f304',
name: 'Collection 4',
metadata: [
{
key: 'dc.title',
language: 'en_US',
value: 'Community 1-Collection 4'
}],
parentCommunity: of(
new RemoteData(false, false, true, undefined, community, 200)
)
}),
Object.assign(new Collection(), {
id: 'a5159760-f362-4659-9e81-e3253ad91ede',
name: 'Collection 5',
metadata: [
{
key: 'dc.title',
language: 'en_US',
value: 'Community 1-Collection 5'
}],
parentCommunity: of(
new RemoteData(false, false, true, undefined, community, 200)
)
})
];

// tslint:disable-next-line: max-classes-per-file
const collectionDataServiceMock = {
getAuthorizedCollection(query: string, options: FindListOptions = {}, ...linksToFollow: Array<FollowLinkConfig<Collection>>): Observable<RemoteData<PaginatedList<Collection>>> {
return hot( 'a|', {
a: createSuccessfulRemoteDataObject(
new PaginatedList(new PageInfo(), collections)
)
});
}
};

beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: TranslateLoaderMock
}
})
],
declarations: [ CollectionSelectorComponent, CollectionDropdownComponent ],
providers: [
{provide: CollectionDataService, useValue: collectionDataServiceMock},
{provide: ChangeDetectorRef, useValue: {}},
{provide: ElementRef, userValue: {}},
{provide: NgbActiveModal, useValue: modal},
{provide: ActivatedRoute, useValue: {}}
],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents();
}));

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

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

it('should call selectObject', fakeAsync(() => {
spyOn(component, 'selectObject');
fixture.detectChanges();
tick();
fixture.whenStable().then(() => {
const collectionItem = fixture.debugElement.query(By.css('.collection-item:nth-child(2)'));
collectionItem.triggerEventHandler('click', {
preventDefault: () => {/**/
}
});
expect(component.selectObject).toHaveBeenCalled();
});
}));

it('should close the dialog', () => {
component.close();
expect((component as any).activeModal.close).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Component } from '@angular/core';
import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';

/**
* This component displays the dialog that shows the list of selectable collections
* on the MyDSpace page
*/
@Component({
selector: 'ds-collection-selector',
templateUrl: './collection-selector.component.html',
styleUrls: ['./collection-selector.component.scss']
})
export class CollectionSelectorComponent {

constructor(protected activeModal: NgbActiveModal) {}

/**
* Method called when an element has been selected from collection list.
* Its close the active modal and send selected value to the component container
* @param dso The selected DSpaceObject
*/
selectObject(dso: DSpaceObject) {
this.activeModal.close(dso);
}

/**
* Close the modal
*/
close() {
this.activeModal.close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
<ds-uploader *ngIf="uploadFilesOptions.url !== ''"
[uploadFilesOptions]="uploadFilesOptions"
(onCompleteItem)="onCompleteItem($event)"
(onUploadError)="onUploadError()"></ds-uploader>
(onUploadError)="onUploadError($event)"
(onFileSelected)="afterFileLoaded($event)"></ds-uploader>

</div>
<div class="add">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, inject, TestBed, tick, fakeAsync } from '@angular/core/testing';
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';

import { Store } from '@ngrx/store';
Expand All @@ -15,18 +15,32 @@ import { createTestComponent } from '../../shared/testing/utils.test';
import { MyDSpaceNewSubmissionComponent } from './my-dspace-new-submission.component';
import { AppState } from '../../app.reducer';
import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock';
import { getMockTranslateService } from '../../shared/mocks/translate.service.mock';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
import { SharedModule } from '../../shared/shared.module';
import { getMockScrollToService } from '../../shared/mocks/scroll-to-service.mock';
import { UploaderService } from '../../shared/uploader/uploader.service';
import { By } from '@angular/platform-browser';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { UploaderComponent } from 'src/app/shared/uploader/uploader.component';

describe('MyDSpaceNewSubmissionComponent test', () => {

const translateService: any = getMockTranslateService();
const translateService: TranslateService = jasmine.createSpyObj('translateService', {
get: (key: string): any => { observableOf(key) },
instant: jasmine.createSpy('instant')
});

const uploader: any = jasmine.createSpyObj('uploader', {
clearQueue: jasmine.createSpy('clearQueue')
});

const modalService = {
open: () => {
return { result: new Promise((res, rej) => {/****/}) };
}
};

const store: Store<AppState> = jasmine.createSpyObj('store', {
/* tslint:disable:no-empty */
dispatch: {},
Expand Down Expand Up @@ -56,11 +70,7 @@ describe('MyDSpaceNewSubmissionComponent test', () => {
{ provide: ScrollToService, useValue: getMockScrollToService() },
{ provide: Store, useValue: store },
{ provide: TranslateService, useValue: translateService },
{
provide: NgbModal, useValue: {
open: () => {/*comment*/}
}
},
{ provide: NgbModal, useValue: modalService },
ChangeDetectorRef,
MyDSpaceNewSubmissionComponent,
UploaderService
Expand Down Expand Up @@ -100,6 +110,10 @@ describe('MyDSpaceNewSubmissionComponent test', () => {
beforeEach(() => {
fixture = TestBed.createComponent(MyDSpaceNewSubmissionComponent);
comp = fixture.componentInstance;
comp.uploadFilesOptions.authToken = 'user-auth-token';
comp.uploadFilesOptions.url = 'https://fake.upload-api.url';
comp.uploaderComponent = TestBed.createComponent(UploaderComponent).componentInstance;
comp.uploaderComponent.uploader = uploader;
});

it('should call app.openDialog', () => {
Expand All @@ -111,6 +125,12 @@ describe('MyDSpaceNewSubmissionComponent test', () => {
});
expect(comp.openDialog).toHaveBeenCalled();
});

it('should show a collection selector if only one file are uploaded', () => {
spyOn((comp as any).modalService, 'open').and.returnValue({ result: new Promise((res, rej) => {/****/}) });
comp.afterFileLoaded(['']);
expect((comp as any).modalService.open).toHaveBeenCalled();
});
});
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import { ChangeDetectorRef, Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
import { ChangeDetectorRef, Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';

import { Subscription } from 'rxjs';
import { first } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { TranslateService } from '@ngx-translate/core';

import { SubmissionState } from '../../submission/submission.reducers';
import { AuthService } from '../../core/auth/auth.service';
import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { NotificationsService } from '../../shared/notifications/notifications.service';
Expand All @@ -15,9 +12,11 @@ import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
import { NotificationType } from '../../shared/notifications/models/notification-type';
import { hasValue } from '../../shared/empty.util';
import { SearchResult } from '../../shared/search/search-result.model';
import { Router } from '@angular/router';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { CreateItemParentSelectorComponent } from 'src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component';
import { CollectionSelectorComponent } from '../collection-selector/collection-selector.component';
import { UploaderComponent } from 'src/app/shared/uploader/uploader.component';
import { UploaderError } from 'src/app/shared/uploader/uploader-error.model';

/**
* This component represents the whole mydspace page header
Expand All @@ -43,6 +42,11 @@ export class MyDSpaceNewSubmissionComponent implements OnDestroy, OnInit {
*/
private sub: Subscription;

/**
* Reference to uploaderComponent
*/
@ViewChild(UploaderComponent, { static: false }) uploaderComponent: UploaderComponent;

/**
* Initialize instance variables
*
Expand All @@ -57,16 +61,15 @@ export class MyDSpaceNewSubmissionComponent implements OnDestroy, OnInit {
private changeDetectorRef: ChangeDetectorRef,
private halService: HALEndpointService,
private notificationsService: NotificationsService,
private store: Store<SubmissionState>,
private translate: TranslateService,
private router: Router,
private modalService: NgbModal) {
}

/**
* Initialize url and Bearer token
*/
ngOnInit() {
this.uploadFilesOptions.autoUpload = false;
this.sub = this.halService.getEndpoint('workspaceitems').pipe(first()).subscribe((url) => {
this.uploadFilesOptions.url = url;
this.uploadFilesOptions.authToken = this.authService.buildAuthHeader();
Expand Down Expand Up @@ -106,8 +109,12 @@ export class MyDSpaceNewSubmissionComponent implements OnDestroy, OnInit {
/**
* Method called on file upload error
*/
public onUploadError() {
this.notificationsService.error(null, this.translate.get('mydspace.upload.upload-failed'));
public onUploadError(error: UploaderError) {
let errorMessageKey = 'mydspace.upload.upload-failed';
if (hasValue(error.status) && error.status === 422) {
errorMessageKey = 'mydspace.upload.upload-failed-manyentries';
}
this.notificationsService.error(null, this.translate.get(errorMessageKey));
}

/**
Expand All @@ -118,6 +125,28 @@ export class MyDSpaceNewSubmissionComponent implements OnDestroy, OnInit {
this.modalService.open(CreateItemParentSelectorComponent);
}

/**
* Method invoked after all file are loaded from upload plugin
*/
afterFileLoaded(items) {
const uploader = this.uploaderComponent.uploader;
if (hasValue(items) && items.length > 1) {
this.notificationsService.error(null, this.translate.get('mydspace.upload.upload-failed-moreonefile'));
uploader.clearQueue();
this.changeDetectorRef.detectChanges();
} else {
const modalRef = this.modalService.open(CollectionSelectorComponent);
// When the dialog are closes its takes the collection selected and
// uploads choosed file after adds owningCollection parameter
modalRef.result.then( (result) => {
uploader.onBuildItemForm = (fileItem: any, form: any) => {
form.append('owningCollection', result.uuid);
};
uploader.uploadAll();
});
}
}

/**
* Unsubscribe from the subscription
*/
Expand Down
Loading

0 comments on commit 4f608eb

Please sign in to comment.