Skip to content

Commit 4f608eb

Browse files
authored
Merge pull request #724 from 4Science/CST-3091
DS-4514 start a new submission via file
2 parents eb98098 + 6422288 commit 4f608eb

12 files changed

+312
-22
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<div>
2+
<div class="modal-header">{{'dso-selector.create.submission.head' | translate}}
3+
<button type="button" class="close" (click)="close()" aria-label="Close">
4+
<span aria-hidden="true">×</span>
5+
</button>
6+
</div>
7+
<div class="modal-body">
8+
<ds-collection-dropdown (selectionChange)="selectObject($event.collection)">
9+
</ds-collection-dropdown>
10+
</div>
11+
</div>

src/app/+my-dspace-page/collection-selector/collection-selector.component.scss

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
2+
3+
import { CollectionSelectorComponent } from './collection-selector.component';
4+
import { CollectionDropdownComponent } from 'src/app/shared/collection-dropdown/collection-dropdown.component';
5+
import { Collection } from 'src/app/core/shared/collection.model';
6+
import { of, Observable } from 'rxjs';
7+
import { RemoteData } from 'src/app/core/data/remote-data';
8+
import { Community } from 'src/app/core/shared/community.model';
9+
import { FindListOptions } from 'src/app/core/data/request.models';
10+
import { FollowLinkConfig } from 'src/app/shared/utils/follow-link-config.model';
11+
import { PaginatedList } from 'src/app/core/data/paginated-list';
12+
import { createSuccessfulRemoteDataObject } from 'src/app/shared/remote-data.utils';
13+
import { PageInfo } from 'src/app/core/shared/page-info.model';
14+
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
15+
import { TranslateLoaderMock } from 'src/app/shared/mocks/translate-loader.mock';
16+
import { CollectionDataService } from 'src/app/core/data/collection-data.service';
17+
import { ChangeDetectorRef, ElementRef, NO_ERRORS_SCHEMA } from '@angular/core';
18+
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
19+
import { ActivatedRoute } from '@angular/router';
20+
import { hot } from 'jasmine-marbles';
21+
import { By } from '@angular/platform-browser';
22+
23+
describe('CollectionSelectorComponent', () => {
24+
let component: CollectionSelectorComponent;
25+
let fixture: ComponentFixture<CollectionSelectorComponent>;
26+
const modal = jasmine.createSpyObj('modal', ['close', 'dismiss']);
27+
28+
const community: Community = Object.assign(new Community(), {
29+
id: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88',
30+
uuid: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88',
31+
name: 'Community 1'
32+
});
33+
34+
const collections: Collection[] = [
35+
Object.assign(new Collection(), {
36+
id: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88',
37+
name: 'Collection 1',
38+
metadata: [
39+
{
40+
key: 'dc.title',
41+
language: 'en_US',
42+
value: 'Community 1-Collection 1'
43+
}],
44+
parentCommunity: of(
45+
new RemoteData(false, false, true, undefined, community, 200)
46+
)
47+
}),
48+
Object.assign(new Collection(), {
49+
id: '59ee713b-ee53-4220-8c3f-9860dc84fe33',
50+
name: 'Collection 2',
51+
metadata: [
52+
{
53+
key: 'dc.title',
54+
language: 'en_US',
55+
value: 'Community 1-Collection 2'
56+
}],
57+
parentCommunity: of(
58+
new RemoteData(false, false, true, undefined, community, 200)
59+
)
60+
}),
61+
Object.assign(new Collection(), {
62+
id: 'e9dbf393-7127-415f-8919-55be34a6e9ed',
63+
name: 'Collection 3',
64+
metadata: [
65+
{
66+
key: 'dc.title',
67+
language: 'en_US',
68+
value: 'Community 1-Collection 3'
69+
}],
70+
parentCommunity: of(
71+
new RemoteData(false, false, true, undefined, community, 200)
72+
)
73+
}),
74+
Object.assign(new Collection(), {
75+
id: '59da2ff0-9bf4-45bf-88be-e35abd33f304',
76+
name: 'Collection 4',
77+
metadata: [
78+
{
79+
key: 'dc.title',
80+
language: 'en_US',
81+
value: 'Community 1-Collection 4'
82+
}],
83+
parentCommunity: of(
84+
new RemoteData(false, false, true, undefined, community, 200)
85+
)
86+
}),
87+
Object.assign(new Collection(), {
88+
id: 'a5159760-f362-4659-9e81-e3253ad91ede',
89+
name: 'Collection 5',
90+
metadata: [
91+
{
92+
key: 'dc.title',
93+
language: 'en_US',
94+
value: 'Community 1-Collection 5'
95+
}],
96+
parentCommunity: of(
97+
new RemoteData(false, false, true, undefined, community, 200)
98+
)
99+
})
100+
];
101+
102+
// tslint:disable-next-line: max-classes-per-file
103+
const collectionDataServiceMock = {
104+
getAuthorizedCollection(query: string, options: FindListOptions = {}, ...linksToFollow: Array<FollowLinkConfig<Collection>>): Observable<RemoteData<PaginatedList<Collection>>> {
105+
return hot( 'a|', {
106+
a: createSuccessfulRemoteDataObject(
107+
new PaginatedList(new PageInfo(), collections)
108+
)
109+
});
110+
}
111+
};
112+
113+
beforeEach(async(() => {
114+
TestBed.configureTestingModule({
115+
imports: [
116+
TranslateModule.forRoot({
117+
loader: {
118+
provide: TranslateLoader,
119+
useClass: TranslateLoaderMock
120+
}
121+
})
122+
],
123+
declarations: [ CollectionSelectorComponent, CollectionDropdownComponent ],
124+
providers: [
125+
{provide: CollectionDataService, useValue: collectionDataServiceMock},
126+
{provide: ChangeDetectorRef, useValue: {}},
127+
{provide: ElementRef, userValue: {}},
128+
{provide: NgbActiveModal, useValue: modal},
129+
{provide: ActivatedRoute, useValue: {}}
130+
],
131+
schemas: [NO_ERRORS_SCHEMA]
132+
})
133+
.compileComponents();
134+
}));
135+
136+
beforeEach(() => {
137+
fixture = TestBed.createComponent(CollectionSelectorComponent);
138+
component = fixture.componentInstance;
139+
fixture.detectChanges();
140+
});
141+
142+
it('should create', () => {
143+
expect(component).toBeTruthy();
144+
});
145+
146+
it('should call selectObject', fakeAsync(() => {
147+
spyOn(component, 'selectObject');
148+
fixture.detectChanges();
149+
tick();
150+
fixture.whenStable().then(() => {
151+
const collectionItem = fixture.debugElement.query(By.css('.collection-item:nth-child(2)'));
152+
collectionItem.triggerEventHandler('click', {
153+
preventDefault: () => {/**/
154+
}
155+
});
156+
expect(component.selectObject).toHaveBeenCalled();
157+
});
158+
}));
159+
160+
it('should close the dialog', () => {
161+
component.close();
162+
expect((component as any).activeModal.close).toHaveBeenCalled();
163+
});
164+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Component } from '@angular/core';
2+
import { DSpaceObject } from '../../core/shared/dspace-object.model';
3+
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
4+
5+
/**
6+
* This component displays the dialog that shows the list of selectable collections
7+
* on the MyDSpace page
8+
*/
9+
@Component({
10+
selector: 'ds-collection-selector',
11+
templateUrl: './collection-selector.component.html',
12+
styleUrls: ['./collection-selector.component.scss']
13+
})
14+
export class CollectionSelectorComponent {
15+
16+
constructor(protected activeModal: NgbActiveModal) {}
17+
18+
/**
19+
* Method called when an element has been selected from collection list.
20+
* Its close the active modal and send selected value to the component container
21+
* @param dso The selected DSpaceObject
22+
*/
23+
selectObject(dso: DSpaceObject) {
24+
this.activeModal.close(dso);
25+
}
26+
27+
/**
28+
* Close the modal
29+
*/
30+
close() {
31+
this.activeModal.close();
32+
}
33+
}

src/app/+my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.html

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
<ds-uploader *ngIf="uploadFilesOptions.url !== ''"
44
[uploadFilesOptions]="uploadFilesOptions"
55
(onCompleteItem)="onCompleteItem($event)"
6-
(onUploadError)="onUploadError()"></ds-uploader>
6+
(onUploadError)="onUploadError($event)"
7+
(onFileSelected)="afterFileLoaded($event)"></ds-uploader>
78

89
</div>
910
<div class="add">

src/app/+my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.spec.ts

+28-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
2-
import { async, ComponentFixture, inject, TestBed, tick, fakeAsync } from '@angular/core/testing';
2+
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
33
import { RouterTestingModule } from '@angular/router/testing';
44

55
import { Store } from '@ngrx/store';
@@ -15,18 +15,32 @@ import { createTestComponent } from '../../shared/testing/utils.test';
1515
import { MyDSpaceNewSubmissionComponent } from './my-dspace-new-submission.component';
1616
import { AppState } from '../../app.reducer';
1717
import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock';
18-
import { getMockTranslateService } from '../../shared/mocks/translate.service.mock';
1918
import { NotificationsService } from '../../shared/notifications/notifications.service';
2019
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
2120
import { SharedModule } from '../../shared/shared.module';
2221
import { getMockScrollToService } from '../../shared/mocks/scroll-to-service.mock';
2322
import { UploaderService } from '../../shared/uploader/uploader.service';
2423
import { By } from '@angular/platform-browser';
2524
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
25+
import { UploaderComponent } from 'src/app/shared/uploader/uploader.component';
2626

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

29-
const translateService: any = getMockTranslateService();
29+
const translateService: TranslateService = jasmine.createSpyObj('translateService', {
30+
get: (key: string): any => { observableOf(key) },
31+
instant: jasmine.createSpy('instant')
32+
});
33+
34+
const uploader: any = jasmine.createSpyObj('uploader', {
35+
clearQueue: jasmine.createSpy('clearQueue')
36+
});
37+
38+
const modalService = {
39+
open: () => {
40+
return { result: new Promise((res, rej) => {/****/}) };
41+
}
42+
};
43+
3044
const store: Store<AppState> = jasmine.createSpyObj('store', {
3145
/* tslint:disable:no-empty */
3246
dispatch: {},
@@ -56,11 +70,7 @@ describe('MyDSpaceNewSubmissionComponent test', () => {
5670
{ provide: ScrollToService, useValue: getMockScrollToService() },
5771
{ provide: Store, useValue: store },
5872
{ provide: TranslateService, useValue: translateService },
59-
{
60-
provide: NgbModal, useValue: {
61-
open: () => {/*comment*/}
62-
}
63-
},
73+
{ provide: NgbModal, useValue: modalService },
6474
ChangeDetectorRef,
6575
MyDSpaceNewSubmissionComponent,
6676
UploaderService
@@ -100,6 +110,10 @@ describe('MyDSpaceNewSubmissionComponent test', () => {
100110
beforeEach(() => {
101111
fixture = TestBed.createComponent(MyDSpaceNewSubmissionComponent);
102112
comp = fixture.componentInstance;
113+
comp.uploadFilesOptions.authToken = 'user-auth-token';
114+
comp.uploadFilesOptions.url = 'https://fake.upload-api.url';
115+
comp.uploaderComponent = TestBed.createComponent(UploaderComponent).componentInstance;
116+
comp.uploaderComponent.uploader = uploader;
103117
});
104118

105119
it('should call app.openDialog', () => {
@@ -111,6 +125,12 @@ describe('MyDSpaceNewSubmissionComponent test', () => {
111125
});
112126
expect(comp.openDialog).toHaveBeenCalled();
113127
});
128+
129+
it('should show a collection selector if only one file are uploaded', () => {
130+
spyOn((comp as any).modalService, 'open').and.returnValue({ result: new Promise((res, rej) => {/****/}) });
131+
comp.afterFileLoaded(['']);
132+
expect((comp as any).modalService.open).toHaveBeenCalled();
133+
});
114134
});
115135
});
116136

src/app/+my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.ts

+38-9
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
import { ChangeDetectorRef, Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
1+
import { ChangeDetectorRef, Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
22

33
import { Subscription } from 'rxjs';
44
import { first } from 'rxjs/operators';
5-
import { Store } from '@ngrx/store';
65
import { TranslateService } from '@ngx-translate/core';
7-
8-
import { SubmissionState } from '../../submission/submission.reducers';
96
import { AuthService } from '../../core/auth/auth.service';
107
import { DSpaceObject } from '../../core/shared/dspace-object.model';
118
import { NotificationsService } from '../../shared/notifications/notifications.service';
@@ -15,9 +12,11 @@ import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
1512
import { NotificationType } from '../../shared/notifications/models/notification-type';
1613
import { hasValue } from '../../shared/empty.util';
1714
import { SearchResult } from '../../shared/search/search-result.model';
18-
import { Router } from '@angular/router';
1915
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
2016
import { CreateItemParentSelectorComponent } from 'src/app/shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component';
17+
import { CollectionSelectorComponent } from '../collection-selector/collection-selector.component';
18+
import { UploaderComponent } from 'src/app/shared/uploader/uploader.component';
19+
import { UploaderError } from 'src/app/shared/uploader/uploader-error.model';
2120

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

45+
/**
46+
* Reference to uploaderComponent
47+
*/
48+
@ViewChild(UploaderComponent, { static: false }) uploaderComponent: UploaderComponent;
49+
4650
/**
4751
* Initialize instance variables
4852
*
@@ -57,16 +61,15 @@ export class MyDSpaceNewSubmissionComponent implements OnDestroy, OnInit {
5761
private changeDetectorRef: ChangeDetectorRef,
5862
private halService: HALEndpointService,
5963
private notificationsService: NotificationsService,
60-
private store: Store<SubmissionState>,
6164
private translate: TranslateService,
62-
private router: Router,
6365
private modalService: NgbModal) {
6466
}
6567

6668
/**
6769
* Initialize url and Bearer token
6870
*/
6971
ngOnInit() {
72+
this.uploadFilesOptions.autoUpload = false;
7073
this.sub = this.halService.getEndpoint('workspaceitems').pipe(first()).subscribe((url) => {
7174
this.uploadFilesOptions.url = url;
7275
this.uploadFilesOptions.authToken = this.authService.buildAuthHeader();
@@ -106,8 +109,12 @@ export class MyDSpaceNewSubmissionComponent implements OnDestroy, OnInit {
106109
/**
107110
* Method called on file upload error
108111
*/
109-
public onUploadError() {
110-
this.notificationsService.error(null, this.translate.get('mydspace.upload.upload-failed'));
112+
public onUploadError(error: UploaderError) {
113+
let errorMessageKey = 'mydspace.upload.upload-failed';
114+
if (hasValue(error.status) && error.status === 422) {
115+
errorMessageKey = 'mydspace.upload.upload-failed-manyentries';
116+
}
117+
this.notificationsService.error(null, this.translate.get(errorMessageKey));
111118
}
112119

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

128+
/**
129+
* Method invoked after all file are loaded from upload plugin
130+
*/
131+
afterFileLoaded(items) {
132+
const uploader = this.uploaderComponent.uploader;
133+
if (hasValue(items) && items.length > 1) {
134+
this.notificationsService.error(null, this.translate.get('mydspace.upload.upload-failed-moreonefile'));
135+
uploader.clearQueue();
136+
this.changeDetectorRef.detectChanges();
137+
} else {
138+
const modalRef = this.modalService.open(CollectionSelectorComponent);
139+
// When the dialog are closes its takes the collection selected and
140+
// uploads choosed file after adds owningCollection parameter
141+
modalRef.result.then( (result) => {
142+
uploader.onBuildItemForm = (fileItem: any, form: any) => {
143+
form.append('owningCollection', result.uuid);
144+
};
145+
uploader.uploadAll();
146+
});
147+
}
148+
}
149+
121150
/**
122151
* Unsubscribe from the subscription
123152
*/

0 commit comments

Comments
 (0)