Skip to content

Commit 86b27de

Browse files
authored
Merge branch 'main' into bugfix/addressing-#2276
2 parents 404e2f8 + e9b18d8 commit 86b27de

37 files changed

Lines changed: 1246 additions & 599 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: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ import { buildAppConfig } from './src/config/config.server';
5353
import { APP_CONFIG, AppConfig } from './src/config/app-config.interface';
5454
import { extendEnvironmentWithAppConfig } from './src/config/config.util';
5555
import { logStartupMessage } from './startup-message';
56-
import { TOKENITEM } from 'src/app/core/auth/models/auth-token-info.model';
56+
import { TOKENITEM } from './src/app/core/auth/models/auth-token-info.model';
5757

5858

5959
/*
@@ -374,9 +374,19 @@ function cacheCheck(req, res, next) {
374374
}
375375

376376
// If cached copy exists, return it to the user.
377-
if (cachedCopy) {
377+
if (cachedCopy && cachedCopy.page) {
378+
if (cachedCopy.headers) {
379+
Object.keys(cachedCopy.headers).forEach((header) => {
380+
if (cachedCopy.headers[header]) {
381+
if (environment.cache.serverSide.debug) {
382+
console.log(`Restore cached ${header} header`);
383+
}
384+
res.setHeader(header, cachedCopy.headers[header]);
385+
}
386+
});
387+
}
378388
res.locals.ssr = true; // mark response as SSR-generated (enables text compression)
379-
res.send(cachedCopy);
389+
res.send(cachedCopy.page);
380390

381391
// Tell Express to skip all other handlers for this path
382392
// This ensures we don't try to re-render the page since we've already returned the cached copy
@@ -452,21 +462,38 @@ function saveToCache(req, page: any) {
452462
// Avoid caching "/reload/[random]" paths (these are hard refreshes after logout)
453463
if (key.startsWith('/reload')) { return; }
454464

465+
// Retrieve response headers to save, if any
466+
const headers = retrieveHeaders(req.res);
455467
// If bot cache is enabled, save it to that cache if it doesn't exist or is expired
456468
// (NOTE: has() will return false if page is expired in cache)
457469
if (botCacheEnabled() && !botCache.has(key)) {
458-
botCache.set(key, page);
470+
botCache.set(key, { page, headers });
459471
if (environment.cache.serverSide.debug) { console.log(`CACHE SAVE FOR ${key} in bot cache.`); }
460472
}
461473

462474
// If anonymous cache is enabled, save it to that cache if it doesn't exist or is expired
463475
if (anonymousCacheEnabled() && !anonymousCache.has(key)) {
464-
anonymousCache.set(key, page);
476+
anonymousCache.set(key, { page, headers });
465477
if (environment.cache.serverSide.debug) { console.log(`CACHE SAVE FOR ${key} in anonymous cache.`); }
466478
}
467479
}
468480
}
469481

482+
function retrieveHeaders(response) {
483+
const headers = Object.create({});
484+
if (Array.isArray(environment.cache.serverSide.headers) && environment.cache.serverSide.headers.length > 0) {
485+
environment.cache.serverSide.headers.forEach((header) => {
486+
if (response.hasHeader(header)) {
487+
if (environment.cache.serverSide.debug) {
488+
console.log(`Save ${header} header to cache`);
489+
}
490+
headers[header] = response.getHeader(header);
491+
}
492+
});
493+
}
494+
495+
return headers;
496+
}
470497
/**
471498
* Whether a user is authenticated or not
472499
*/

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: [

src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ export class BitstreamDownloadPageComponent implements OnInit {
108108

109109
signpostingLinks.forEach((link: SignpostingLink) => {
110110
links = links + (isNotEmpty(links) ? ', ' : '') + `<${link.href}> ; rel="${link.rel}"` + (isNotEmpty(link.type) ? ` ; type="${link.type}" ` : ' ');
111-
links = links + (isNotEmpty(links) ? ', ' : '') + `<${link.href}> ; rel="${link.rel}" ; type="${link.type}" `;
112111
});
113112

114113
this.responseService.setHeader('Link', links);

src/app/bitstream-page/bitstream-page.resolver.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { getFirstCompletedRemoteData } from '../core/shared/operators';
1212
* Requesting them as embeds will limit the number of requests
1313
*/
1414
export const BITSTREAM_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Bitstream>[] = [
15-
followLink('bundle', {}, followLink('item')),
15+
followLink('bundle', {}, followLink('primaryBitstream'), followLink('item')),
1616
followLink('format')
1717
];
1818

0 commit comments

Comments
 (0)