Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DS-4514 start a new submission via file #724

Merged
merged 11 commits into from
Aug 6, 2020
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>
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,29 @@
import { Component } from '@angular/core';
import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';

@Component({
selector: 'ds-collection-selector',
templateUrl: './collection-selector.component.html',
styleUrls: ['./collection-selector.component.scss']
})
export class CollectionSelectorComponent {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a TypeDocs description to this class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


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'));
tdonohue marked this conversation as resolved.
Show resolved Hide resolved
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