Skip to content

Commit e720c77

Browse files
committed
Merge branch 'main' into feature/CST-9636
# Conflicts: # src/assets/i18n/en.json5
2 parents 706c49d + 9919abe commit e720c77

27 files changed

Lines changed: 967 additions & 449 deletions

cypress/e2e/homepage.cy.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ describe('Homepage', () => {
66
cy.visit('/');
77
});
88

9-
it('should display translated title "DSpace Angular :: Home"', () => {
10-
cy.title().should('eq', 'DSpace Angular :: Home');
9+
it('should display translated title "DSpace Repository :: Home"', () => {
10+
cy.title().should('eq', 'DSpace Repository :: Home');
1111
});
1212

1313
it('should contain a news section', () => {

server.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,14 @@ import * as ejs from 'ejs';
2626
import * as compression from 'compression';
2727
import * as expressStaticGzip from 'express-static-gzip';
2828
/* eslint-enable import/no-namespace */
29-
3029
import axios from 'axios';
3130
import LRU from 'lru-cache';
3231
import isbot from 'isbot';
3332
import { createCertificate } from 'pem';
3433
import { createServer } from 'https';
3534
import { json } from 'body-parser';
3635

37-
import { existsSync, readFileSync } from 'fs';
36+
import { readFileSync } from 'fs';
3837
import { join } from 'path';
3938

4039
import { enableProdMode } from '@angular/core';
@@ -180,6 +179,15 @@ export function app() {
180179
changeOrigin: true
181180
}));
182181

182+
/**
183+
* Proxy the linksets
184+
*/
185+
router.use('/signposting**', createProxyMiddleware({
186+
target: `${environment.rest.baseUrl}`,
187+
pathRewrite: path => path.replace(environment.ui.nameSpace, '/'),
188+
changeOrigin: true
189+
}));
190+
183191
/**
184192
* Checks if the rateLimiter property is present
185193
* When it is present, the rateLimiter will be enabled. When it is undefined, the rateLimiter will be disabled.

src/app/access-control/epeople-registry/epeople-registry.component.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -287,14 +287,17 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
287287
/**
288288
* This method will set everything to stale, which will cause the lists on this page to update.
289289
*/
290-
reset() {
290+
reset(): void {
291291
this.epersonService.getBrowseEndpoint().pipe(
292-
take(1)
293-
).subscribe((href: string) => {
294-
this.requestService.setStaleByHrefSubstring(href).pipe(take(1)).subscribe(() => {
295-
this.epersonService.cancelEditEPerson();
296-
this.isEPersonFormShown = false;
297-
});
292+
take(1),
293+
switchMap((href: string) => {
294+
return this.requestService.setStaleByHrefSubstring(href).pipe(
295+
take(1),
296+
);
297+
})
298+
).subscribe(()=>{
299+
this.epersonService.cancelEditEPerson();
300+
this.isEPersonFormShown = false;
298301
});
299302
}
300303
}

src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
} from '@ng-dynamic-forms/core';
99
import { TranslateService } from '@ngx-translate/core';
1010
import { combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs';
11-
import { debounceTime, switchMap, take } from 'rxjs/operators';
11+
import { debounceTime, finalize, map, switchMap, take } from 'rxjs/operators';
1212
import { PaginatedList } from '../../../core/data/paginated-list.model';
1313
import { RemoteData } from '../../../core/data/remote-data';
1414
import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
@@ -463,31 +463,42 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
463463
* Deletes the EPerson from the Repository. The EPerson will be the only that this form is showing.
464464
* It'll either show a success or error message depending on whether the delete was successful or not.
465465
*/
466-
delete() {
467-
this.epersonService.getActiveEPerson().pipe(take(1)).subscribe((eperson: EPerson) => {
468-
const modalRef = this.modalService.open(ConfirmationModalComponent);
469-
modalRef.componentInstance.dso = eperson;
470-
modalRef.componentInstance.headerLabel = 'confirmation-modal.delete-eperson.header';
471-
modalRef.componentInstance.infoLabel = 'confirmation-modal.delete-eperson.info';
472-
modalRef.componentInstance.cancelLabel = 'confirmation-modal.delete-eperson.cancel';
473-
modalRef.componentInstance.confirmLabel = 'confirmation-modal.delete-eperson.confirm';
474-
modalRef.componentInstance.brandColor = 'danger';
475-
modalRef.componentInstance.confirmIcon = 'fas fa-trash';
476-
modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => {
477-
if (confirm) {
478-
if (hasValue(eperson.id)) {
479-
this.epersonService.deleteEPerson(eperson).pipe(getFirstCompletedRemoteData()).subscribe((restResponse: RemoteData<NoContent>) => {
480-
if (restResponse.hasSucceeded) {
481-
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', { name: this.dsoNameService.getName(eperson) }));
482-
this.submitForm.emit();
483-
} else {
484-
this.notificationsService.error('Error occured when trying to delete EPerson with id: ' + eperson.id + ' with code: ' + restResponse.statusCode + ' and message: ' + restResponse.errorMessage);
485-
}
486-
this.cancelForm.emit();
487-
});
488-
}
489-
}
490-
});
466+
delete(): void {
467+
this.epersonService.getActiveEPerson().pipe(
468+
take(1),
469+
switchMap((eperson: EPerson) => {
470+
const modalRef = this.modalService.open(ConfirmationModalComponent);
471+
modalRef.componentInstance.dso = eperson;
472+
modalRef.componentInstance.headerLabel = 'confirmation-modal.delete-eperson.header';
473+
modalRef.componentInstance.infoLabel = 'confirmation-modal.delete-eperson.info';
474+
modalRef.componentInstance.cancelLabel = 'confirmation-modal.delete-eperson.cancel';
475+
modalRef.componentInstance.confirmLabel = 'confirmation-modal.delete-eperson.confirm';
476+
modalRef.componentInstance.brandColor = 'danger';
477+
modalRef.componentInstance.confirmIcon = 'fas fa-trash';
478+
479+
return modalRef.componentInstance.response.pipe(
480+
take(1),
481+
switchMap((confirm: boolean) => {
482+
if (confirm && hasValue(eperson.id)) {
483+
this.canDelete$ = observableOf(false);
484+
return this.epersonService.deleteEPerson(eperson).pipe(
485+
getFirstCompletedRemoteData(),
486+
map((restResponse: RemoteData<NoContent>) => ({ restResponse, eperson }))
487+
);
488+
} else {
489+
return observableOf(null);
490+
}
491+
}),
492+
finalize(() => this.canDelete$ = observableOf(true))
493+
);
494+
})
495+
).subscribe(({ restResponse, eperson }: { restResponse: RemoteData<NoContent> | null, eperson: EPerson }) => {
496+
if (restResponse?.hasSucceeded) {
497+
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', { name: this.dsoNameService.getName(eperson) }));
498+
} else {
499+
this.notificationsService.error(`Error occurred when trying to delete EPerson with id: ${eperson?.id} with code: ${restResponse?.statusCode} and message: ${restResponse?.errorMessage}`);
500+
}
501+
this.cancelForm.emit();
491502
});
492503
}
493504

@@ -523,7 +534,6 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
523534
* Cancel the current edit when component is destroyed & unsub all subscriptions
524535
*/
525536
ngOnDestroy(): void {
526-
this.onCancel();
527537
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
528538
this.paginationService.clearPagination(this.config.id);
529539
if (hasValue(this.emailValueChangeSubscribe)) {

src/app/admin/admin-import-batch-page/batch-import-page.component.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,29 @@ <h2 id="header">{{'admin.batch-import.page.header' | translate}}</h2>
2020
</small>
2121
</div>
2222

23+
<ui-switch color="#ebebeb"
24+
[checkedLabel]="'admin.metadata-import.page.toggle.upload' | translate"
25+
[uncheckedLabel]="'admin.metadata-import.page.toggle.url' | translate"
26+
[checked]="isUpload"
27+
(change)="toggleUpload()" ></ui-switch>
28+
<small class="form-text text-muted">
29+
{{'admin.batch-import.page.toggle.help' | translate}}
30+
</small>
31+
32+
2333
<ds-file-dropzone-no-uploader
34+
*ngIf="isUpload"
35+
data-test="file-dropzone"
2436
(onFileAdded)="setFile($event)"
2537
[dropMessageLabel]="'admin.batch-import.page.dropMsg'"
2638
[dropMessageLabelReplacement]="'admin.batch-import.page.dropMsgReplace'">
2739
</ds-file-dropzone-no-uploader>
2840

41+
<div class="form-group mt-2" *ngIf="!isUpload">
42+
<input class="form-control" type="text" placeholder="{{'admin.metadata-import.page.urlMsg' | translate}}"
43+
data-test="file-url-input" [(ngModel)]="fileURL">
44+
</div>
45+
2946
<div class="space-children-mr">
3047
<button class="btn btn-secondary" id="backButton"
3148
(click)="this.onReturn();">{{'admin.metadata-import.page.button.return' | translate}}</button>

src/app/admin/admin-import-batch-page/batch-import-page.component.spec.ts

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,18 @@ describe('BatchImportPageComponent', () => {
8686
let fileMock: File;
8787

8888
beforeEach(() => {
89+
component.isUpload = true;
8990
fileMock = new File([''], 'filename.zip', { type: 'application/zip' });
9091
component.setFile(fileMock);
9192
});
9293

94+
it('should show the file dropzone', () => {
95+
const fileDropzone = fixture.debugElement.query(By.css('[data-test="file-dropzone"]'));
96+
const fileUrlInput = fixture.debugElement.query(By.css('[data-test="file-url-input"]'));
97+
expect(fileDropzone).toBeTruthy();
98+
expect(fileUrlInput).toBeFalsy();
99+
});
100+
93101
describe('if proceed button is pressed without validate only', () => {
94102
beforeEach(fakeAsync(() => {
95103
component.validateOnly = false;
@@ -99,9 +107,9 @@ describe('BatchImportPageComponent', () => {
99107
}));
100108
it('metadata-import script is invoked with --zip fileName and the mockFile', () => {
101109
const parameterValues: ProcessParameter[] = [
102-
Object.assign(new ProcessParameter(), { name: '--zip', value: 'filename.zip' }),
110+
Object.assign(new ProcessParameter(), { name: '--add' }),
111+
Object.assign(new ProcessParameter(), { name: '--zip', value: 'filename.zip' })
103112
];
104-
parameterValues.push(Object.assign(new ProcessParameter(), { name: '--add' }));
105113
expect(scriptService.invoke).toHaveBeenCalledWith(BATCH_IMPORT_SCRIPT_NAME, parameterValues, [fileMock]);
106114
});
107115
it('success notification is shown', () => {
@@ -121,8 +129,8 @@ describe('BatchImportPageComponent', () => {
121129
}));
122130
it('metadata-import script is invoked with --zip fileName and the mockFile and -v validate-only', () => {
123131
const parameterValues: ProcessParameter[] = [
124-
Object.assign(new ProcessParameter(), { name: '--zip', value: 'filename.zip' }),
125132
Object.assign(new ProcessParameter(), { name: '--add' }),
133+
Object.assign(new ProcessParameter(), { name: '--zip', value: 'filename.zip' }),
126134
Object.assign(new ProcessParameter(), { name: '-v', value: true }),
127135
];
128136
expect(scriptService.invoke).toHaveBeenCalledWith(BATCH_IMPORT_SCRIPT_NAME, parameterValues, [fileMock]);
@@ -148,4 +156,77 @@ describe('BatchImportPageComponent', () => {
148156
});
149157
});
150158
});
159+
160+
describe('if url is set', () => {
161+
beforeEach(fakeAsync(() => {
162+
component.isUpload = false;
163+
component.fileURL = 'example.fileURL.com';
164+
fixture.detectChanges();
165+
}));
166+
167+
it('should show the file url input', () => {
168+
const fileDropzone = fixture.debugElement.query(By.css('[data-test="file-dropzone"]'));
169+
const fileUrlInput = fixture.debugElement.query(By.css('[data-test="file-url-input"]'));
170+
expect(fileDropzone).toBeFalsy();
171+
expect(fileUrlInput).toBeTruthy();
172+
});
173+
174+
describe('if proceed button is pressed without validate only', () => {
175+
beforeEach(fakeAsync(() => {
176+
component.validateOnly = false;
177+
const proceed = fixture.debugElement.query(By.css('#proceedButton')).nativeElement;
178+
proceed.click();
179+
fixture.detectChanges();
180+
}));
181+
it('metadata-import script is invoked with --url and the file url', () => {
182+
const parameterValues: ProcessParameter[] = [
183+
Object.assign(new ProcessParameter(), { name: '--add' }),
184+
Object.assign(new ProcessParameter(), { name: '--url', value: 'example.fileURL.com' })
185+
];
186+
expect(scriptService.invoke).toHaveBeenCalledWith(BATCH_IMPORT_SCRIPT_NAME, parameterValues, [null]);
187+
});
188+
it('success notification is shown', () => {
189+
expect(notificationService.success).toHaveBeenCalled();
190+
});
191+
it('redirected to process page', () => {
192+
expect(router.navigateByUrl).toHaveBeenCalledWith('/processes/46');
193+
});
194+
});
195+
196+
describe('if proceed button is pressed with validate only', () => {
197+
beforeEach(fakeAsync(() => {
198+
component.validateOnly = true;
199+
const proceed = fixture.debugElement.query(By.css('#proceedButton')).nativeElement;
200+
proceed.click();
201+
fixture.detectChanges();
202+
}));
203+
it('metadata-import script is invoked with --url and the file url and -v validate-only', () => {
204+
const parameterValues: ProcessParameter[] = [
205+
Object.assign(new ProcessParameter(), { name: '--add' }),
206+
Object.assign(new ProcessParameter(), { name: '--url', value: 'example.fileURL.com' }),
207+
Object.assign(new ProcessParameter(), { name: '-v', value: true }),
208+
];
209+
expect(scriptService.invoke).toHaveBeenCalledWith(BATCH_IMPORT_SCRIPT_NAME, parameterValues, [null]);
210+
});
211+
it('success notification is shown', () => {
212+
expect(notificationService.success).toHaveBeenCalled();
213+
});
214+
it('redirected to process page', () => {
215+
expect(router.navigateByUrl).toHaveBeenCalledWith('/processes/46');
216+
});
217+
});
218+
219+
describe('if proceed is pressed; but script invoke fails', () => {
220+
beforeEach(fakeAsync(() => {
221+
jasmine.getEnv().allowRespy(true);
222+
spyOn(scriptService, 'invoke').and.returnValue(createFailedRemoteDataObject$('Error', 500));
223+
const proceed = fixture.debugElement.query(By.css('#proceedButton')).nativeElement;
224+
proceed.click();
225+
fixture.detectChanges();
226+
}));
227+
it('error notification is shown', () => {
228+
expect(notificationService.error).toHaveBeenCalled();
229+
});
230+
});
231+
});
151232
});

src/app/admin/admin-import-batch-page/batch-import-page.component.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { ProcessParameter } from '../../process-page/processes/process-parameter
88
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
99
import { RemoteData } from '../../core/data/remote-data';
1010
import { Process } from '../../process-page/processes/process.model';
11-
import { isNotEmpty } from '../../shared/empty.util';
11+
import { isEmpty, isNotEmpty } from '../../shared/empty.util';
1212
import { getProcessDetailRoute } from '../../process-page/process-page-routing.paths';
1313
import {
1414
ImportBatchSelectorComponent
@@ -32,11 +32,22 @@ export class BatchImportPageComponent {
3232
* The validate only flag
3333
*/
3434
validateOnly = true;
35+
3536
/**
3637
* dso object for community or collection
3738
*/
3839
dso: DSpaceObject = null;
3940

41+
/**
42+
* The flag between upload and url
43+
*/
44+
isUpload = true;
45+
46+
/**
47+
* File URL when flag is for url
48+
*/
49+
fileURL: string;
50+
4051
public constructor(private location: Location,
4152
protected translate: TranslateService,
4253
protected notificationsService: NotificationsService,
@@ -72,13 +83,22 @@ export class BatchImportPageComponent {
7283
* Starts import-metadata script with --zip fileName (and the selected file)
7384
*/
7485
public importMetadata() {
75-
if (this.fileObject == null) {
76-
this.notificationsService.error(this.translate.get('admin.metadata-import.page.error.addFile'));
86+
if (this.fileObject == null && isEmpty(this.fileURL)) {
87+
if (this.isUpload) {
88+
this.notificationsService.error(this.translate.get('admin.metadata-import.page.error.addFile'));
89+
} else {
90+
this.notificationsService.error(this.translate.get('admin.metadata-import.page.error.addFileUrl'));
91+
}
7792
} else {
7893
const parameterValues: ProcessParameter[] = [
79-
Object.assign(new ProcessParameter(), { name: '--zip', value: this.fileObject.name }),
8094
Object.assign(new ProcessParameter(), { name: '--add' })
8195
];
96+
if (this.isUpload) {
97+
parameterValues.push(Object.assign(new ProcessParameter(), { name: '--zip', value: this.fileObject.name }));
98+
} else {
99+
this.fileObject = null;
100+
parameterValues.push(Object.assign(new ProcessParameter(), { name: '--url', value: this.fileURL }));
101+
}
82102
if (this.dso) {
83103
parameterValues.push(Object.assign(new ProcessParameter(), { name: '--collection', value: this.dso.uuid }));
84104
}
@@ -127,4 +147,11 @@ export class BatchImportPageComponent {
127147
removeDspaceObject(): void {
128148
this.dso = null;
129149
}
150+
151+
/**
152+
* toggle the flag between upload and url
153+
*/
154+
toggleUpload() {
155+
this.isUpload = !this.isUpload;
156+
}
130157
}

src/app/admin/admin.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { AdminSearchModule } from './admin-search-page/admin-search.module';
1010
import { AdminSidebarSectionComponent } from './admin-sidebar/admin-sidebar-section/admin-sidebar-section.component';
1111
import { ExpandableAdminSidebarSectionComponent } from './admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component';
1212
import { BatchImportPageComponent } from './admin-import-batch-page/batch-import-page.component';
13+
import { UiSwitchModule } from 'ngx-ui-switch';
1314
import { UploadModule } from '../shared/upload/upload.module';
1415

1516
const ENTRY_COMPONENTS = [
@@ -27,6 +28,7 @@ const ENTRY_COMPONENTS = [
2728
AdminSearchModule.withEntryComponents(),
2829
AdminWorkflowModuleModule.withEntryComponents(),
2930
SharedModule,
31+
UiSwitchModule,
3032
UploadModule,
3133
],
3234
declarations: [

0 commit comments

Comments
 (0)