mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge branch 'main' into bugfix/addressing-#2276
This commit is contained in:
@@ -6,8 +6,8 @@ describe('Homepage', () => {
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
it('should display translated title "DSpace Angular :: Home"', () => {
|
||||
cy.title().should('eq', 'DSpace Angular :: Home');
|
||||
it('should display translated title "DSpace Repository :: Home"', () => {
|
||||
cy.title().should('eq', 'DSpace Repository :: Home');
|
||||
});
|
||||
|
||||
it('should contain a news section', () => {
|
||||
|
37
server.ts
37
server.ts
@@ -53,7 +53,7 @@ import { buildAppConfig } from './src/config/config.server';
|
||||
import { APP_CONFIG, AppConfig } from './src/config/app-config.interface';
|
||||
import { extendEnvironmentWithAppConfig } from './src/config/config.util';
|
||||
import { logStartupMessage } from './startup-message';
|
||||
import { TOKENITEM } from 'src/app/core/auth/models/auth-token-info.model';
|
||||
import { TOKENITEM } from './src/app/core/auth/models/auth-token-info.model';
|
||||
|
||||
|
||||
/*
|
||||
@@ -374,9 +374,19 @@ function cacheCheck(req, res, next) {
|
||||
}
|
||||
|
||||
// If cached copy exists, return it to the user.
|
||||
if (cachedCopy) {
|
||||
if (cachedCopy && cachedCopy.page) {
|
||||
if (cachedCopy.headers) {
|
||||
Object.keys(cachedCopy.headers).forEach((header) => {
|
||||
if (cachedCopy.headers[header]) {
|
||||
if (environment.cache.serverSide.debug) {
|
||||
console.log(`Restore cached ${header} header`);
|
||||
}
|
||||
res.setHeader(header, cachedCopy.headers[header]);
|
||||
}
|
||||
});
|
||||
}
|
||||
res.locals.ssr = true; // mark response as SSR-generated (enables text compression)
|
||||
res.send(cachedCopy);
|
||||
res.send(cachedCopy.page);
|
||||
|
||||
// Tell Express to skip all other handlers for this path
|
||||
// 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) {
|
||||
// Avoid caching "/reload/[random]" paths (these are hard refreshes after logout)
|
||||
if (key.startsWith('/reload')) { return; }
|
||||
|
||||
// Retrieve response headers to save, if any
|
||||
const headers = retrieveHeaders(req.res);
|
||||
// If bot cache is enabled, save it to that cache if it doesn't exist or is expired
|
||||
// (NOTE: has() will return false if page is expired in cache)
|
||||
if (botCacheEnabled() && !botCache.has(key)) {
|
||||
botCache.set(key, page);
|
||||
botCache.set(key, { page, headers });
|
||||
if (environment.cache.serverSide.debug) { console.log(`CACHE SAVE FOR ${key} in bot cache.`); }
|
||||
}
|
||||
|
||||
// If anonymous cache is enabled, save it to that cache if it doesn't exist or is expired
|
||||
if (anonymousCacheEnabled() && !anonymousCache.has(key)) {
|
||||
anonymousCache.set(key, page);
|
||||
anonymousCache.set(key, { page, headers });
|
||||
if (environment.cache.serverSide.debug) { console.log(`CACHE SAVE FOR ${key} in anonymous cache.`); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function retrieveHeaders(response) {
|
||||
const headers = Object.create({});
|
||||
if (Array.isArray(environment.cache.serverSide.headers) && environment.cache.serverSide.headers.length > 0) {
|
||||
environment.cache.serverSide.headers.forEach((header) => {
|
||||
if (response.hasHeader(header)) {
|
||||
if (environment.cache.serverSide.debug) {
|
||||
console.log(`Save ${header} header to cache`);
|
||||
}
|
||||
headers[header] = response.getHeader(header);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
/**
|
||||
* Whether a user is authenticated or not
|
||||
*/
|
||||
|
@@ -20,12 +20,29 @@
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<ui-switch color="#ebebeb"
|
||||
[checkedLabel]="'admin.metadata-import.page.toggle.upload' | translate"
|
||||
[uncheckedLabel]="'admin.metadata-import.page.toggle.url' | translate"
|
||||
[checked]="isUpload"
|
||||
(change)="toggleUpload()" ></ui-switch>
|
||||
<small class="form-text text-muted">
|
||||
{{'admin.batch-import.page.toggle.help' | translate}}
|
||||
</small>
|
||||
|
||||
|
||||
<ds-file-dropzone-no-uploader
|
||||
*ngIf="isUpload"
|
||||
data-test="file-dropzone"
|
||||
(onFileAdded)="setFile($event)"
|
||||
[dropMessageLabel]="'admin.batch-import.page.dropMsg'"
|
||||
[dropMessageLabelReplacement]="'admin.batch-import.page.dropMsgReplace'">
|
||||
</ds-file-dropzone-no-uploader>
|
||||
|
||||
<div class="form-group mt-2" *ngIf="!isUpload">
|
||||
<input class="form-control" type="text" placeholder="{{'admin.metadata-import.page.urlMsg' | translate}}"
|
||||
data-test="file-url-input" [(ngModel)]="fileURL">
|
||||
</div>
|
||||
|
||||
<div class="space-children-mr">
|
||||
<button class="btn btn-secondary" id="backButton"
|
||||
(click)="this.onReturn();">{{'admin.metadata-import.page.button.return' | translate}}</button>
|
||||
|
@@ -86,10 +86,18 @@ describe('BatchImportPageComponent', () => {
|
||||
let fileMock: File;
|
||||
|
||||
beforeEach(() => {
|
||||
component.isUpload = true;
|
||||
fileMock = new File([''], 'filename.zip', { type: 'application/zip' });
|
||||
component.setFile(fileMock);
|
||||
});
|
||||
|
||||
it('should show the file dropzone', () => {
|
||||
const fileDropzone = fixture.debugElement.query(By.css('[data-test="file-dropzone"]'));
|
||||
const fileUrlInput = fixture.debugElement.query(By.css('[data-test="file-url-input"]'));
|
||||
expect(fileDropzone).toBeTruthy();
|
||||
expect(fileUrlInput).toBeFalsy();
|
||||
});
|
||||
|
||||
describe('if proceed button is pressed without validate only', () => {
|
||||
beforeEach(fakeAsync(() => {
|
||||
component.validateOnly = false;
|
||||
@@ -99,9 +107,9 @@ describe('BatchImportPageComponent', () => {
|
||||
}));
|
||||
it('metadata-import script is invoked with --zip fileName and the mockFile', () => {
|
||||
const parameterValues: ProcessParameter[] = [
|
||||
Object.assign(new ProcessParameter(), { name: '--zip', value: 'filename.zip' }),
|
||||
Object.assign(new ProcessParameter(), { name: '--add' }),
|
||||
Object.assign(new ProcessParameter(), { name: '--zip', value: 'filename.zip' })
|
||||
];
|
||||
parameterValues.push(Object.assign(new ProcessParameter(), { name: '--add' }));
|
||||
expect(scriptService.invoke).toHaveBeenCalledWith(BATCH_IMPORT_SCRIPT_NAME, parameterValues, [fileMock]);
|
||||
});
|
||||
it('success notification is shown', () => {
|
||||
@@ -121,8 +129,8 @@ describe('BatchImportPageComponent', () => {
|
||||
}));
|
||||
it('metadata-import script is invoked with --zip fileName and the mockFile and -v validate-only', () => {
|
||||
const parameterValues: ProcessParameter[] = [
|
||||
Object.assign(new ProcessParameter(), { name: '--zip', value: 'filename.zip' }),
|
||||
Object.assign(new ProcessParameter(), { name: '--add' }),
|
||||
Object.assign(new ProcessParameter(), { name: '--zip', value: 'filename.zip' }),
|
||||
Object.assign(new ProcessParameter(), { name: '-v', value: true }),
|
||||
];
|
||||
expect(scriptService.invoke).toHaveBeenCalledWith(BATCH_IMPORT_SCRIPT_NAME, parameterValues, [fileMock]);
|
||||
@@ -148,4 +156,77 @@ describe('BatchImportPageComponent', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('if url is set', () => {
|
||||
beforeEach(fakeAsync(() => {
|
||||
component.isUpload = false;
|
||||
component.fileURL = 'example.fileURL.com';
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should show the file url input', () => {
|
||||
const fileDropzone = fixture.debugElement.query(By.css('[data-test="file-dropzone"]'));
|
||||
const fileUrlInput = fixture.debugElement.query(By.css('[data-test="file-url-input"]'));
|
||||
expect(fileDropzone).toBeFalsy();
|
||||
expect(fileUrlInput).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('if proceed button is pressed without validate only', () => {
|
||||
beforeEach(fakeAsync(() => {
|
||||
component.validateOnly = false;
|
||||
const proceed = fixture.debugElement.query(By.css('#proceedButton')).nativeElement;
|
||||
proceed.click();
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
it('metadata-import script is invoked with --url and the file url', () => {
|
||||
const parameterValues: ProcessParameter[] = [
|
||||
Object.assign(new ProcessParameter(), { name: '--add' }),
|
||||
Object.assign(new ProcessParameter(), { name: '--url', value: 'example.fileURL.com' })
|
||||
];
|
||||
expect(scriptService.invoke).toHaveBeenCalledWith(BATCH_IMPORT_SCRIPT_NAME, parameterValues, [null]);
|
||||
});
|
||||
it('success notification is shown', () => {
|
||||
expect(notificationService.success).toHaveBeenCalled();
|
||||
});
|
||||
it('redirected to process page', () => {
|
||||
expect(router.navigateByUrl).toHaveBeenCalledWith('/processes/46');
|
||||
});
|
||||
});
|
||||
|
||||
describe('if proceed button is pressed with validate only', () => {
|
||||
beforeEach(fakeAsync(() => {
|
||||
component.validateOnly = true;
|
||||
const proceed = fixture.debugElement.query(By.css('#proceedButton')).nativeElement;
|
||||
proceed.click();
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
it('metadata-import script is invoked with --url and the file url and -v validate-only', () => {
|
||||
const parameterValues: ProcessParameter[] = [
|
||||
Object.assign(new ProcessParameter(), { name: '--add' }),
|
||||
Object.assign(new ProcessParameter(), { name: '--url', value: 'example.fileURL.com' }),
|
||||
Object.assign(new ProcessParameter(), { name: '-v', value: true }),
|
||||
];
|
||||
expect(scriptService.invoke).toHaveBeenCalledWith(BATCH_IMPORT_SCRIPT_NAME, parameterValues, [null]);
|
||||
});
|
||||
it('success notification is shown', () => {
|
||||
expect(notificationService.success).toHaveBeenCalled();
|
||||
});
|
||||
it('redirected to process page', () => {
|
||||
expect(router.navigateByUrl).toHaveBeenCalledWith('/processes/46');
|
||||
});
|
||||
});
|
||||
|
||||
describe('if proceed is pressed; but script invoke fails', () => {
|
||||
beforeEach(fakeAsync(() => {
|
||||
jasmine.getEnv().allowRespy(true);
|
||||
spyOn(scriptService, 'invoke').and.returnValue(createFailedRemoteDataObject$('Error', 500));
|
||||
const proceed = fixture.debugElement.query(By.css('#proceedButton')).nativeElement;
|
||||
proceed.click();
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
it('error notification is shown', () => {
|
||||
expect(notificationService.error).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -8,7 +8,7 @@ import { ProcessParameter } from '../../process-page/processes/process-parameter
|
||||
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Process } from '../../process-page/processes/process.model';
|
||||
import { isNotEmpty } from '../../shared/empty.util';
|
||||
import { isEmpty, isNotEmpty } from '../../shared/empty.util';
|
||||
import { getProcessDetailRoute } from '../../process-page/process-page-routing.paths';
|
||||
import {
|
||||
ImportBatchSelectorComponent
|
||||
@@ -32,11 +32,22 @@ export class BatchImportPageComponent {
|
||||
* The validate only flag
|
||||
*/
|
||||
validateOnly = true;
|
||||
|
||||
/**
|
||||
* dso object for community or collection
|
||||
*/
|
||||
dso: DSpaceObject = null;
|
||||
|
||||
/**
|
||||
* The flag between upload and url
|
||||
*/
|
||||
isUpload = true;
|
||||
|
||||
/**
|
||||
* File URL when flag is for url
|
||||
*/
|
||||
fileURL: string;
|
||||
|
||||
public constructor(private location: Location,
|
||||
protected translate: TranslateService,
|
||||
protected notificationsService: NotificationsService,
|
||||
@@ -72,13 +83,22 @@ export class BatchImportPageComponent {
|
||||
* Starts import-metadata script with --zip fileName (and the selected file)
|
||||
*/
|
||||
public importMetadata() {
|
||||
if (this.fileObject == null) {
|
||||
this.notificationsService.error(this.translate.get('admin.metadata-import.page.error.addFile'));
|
||||
if (this.fileObject == null && isEmpty(this.fileURL)) {
|
||||
if (this.isUpload) {
|
||||
this.notificationsService.error(this.translate.get('admin.metadata-import.page.error.addFile'));
|
||||
} else {
|
||||
this.notificationsService.error(this.translate.get('admin.metadata-import.page.error.addFileUrl'));
|
||||
}
|
||||
} else {
|
||||
const parameterValues: ProcessParameter[] = [
|
||||
Object.assign(new ProcessParameter(), { name: '--zip', value: this.fileObject.name }),
|
||||
Object.assign(new ProcessParameter(), { name: '--add' })
|
||||
];
|
||||
if (this.isUpload) {
|
||||
parameterValues.push(Object.assign(new ProcessParameter(), { name: '--zip', value: this.fileObject.name }));
|
||||
} else {
|
||||
this.fileObject = null;
|
||||
parameterValues.push(Object.assign(new ProcessParameter(), { name: '--url', value: this.fileURL }));
|
||||
}
|
||||
if (this.dso) {
|
||||
parameterValues.push(Object.assign(new ProcessParameter(), { name: '--collection', value: this.dso.uuid }));
|
||||
}
|
||||
@@ -127,4 +147,11 @@ export class BatchImportPageComponent {
|
||||
removeDspaceObject(): void {
|
||||
this.dso = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* toggle the flag between upload and url
|
||||
*/
|
||||
toggleUpload() {
|
||||
this.isUpload = !this.isUpload;
|
||||
}
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@ import { AdminSearchModule } from './admin-search-page/admin-search.module';
|
||||
import { AdminSidebarSectionComponent } from './admin-sidebar/admin-sidebar-section/admin-sidebar-section.component';
|
||||
import { ExpandableAdminSidebarSectionComponent } from './admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component';
|
||||
import { BatchImportPageComponent } from './admin-import-batch-page/batch-import-page.component';
|
||||
import { UiSwitchModule } from 'ngx-ui-switch';
|
||||
import { UploadModule } from '../shared/upload/upload.module';
|
||||
|
||||
const ENTRY_COMPONENTS = [
|
||||
@@ -27,6 +28,7 @@ const ENTRY_COMPONENTS = [
|
||||
AdminSearchModule.withEntryComponents(),
|
||||
AdminWorkflowModuleModule.withEntryComponents(),
|
||||
SharedModule,
|
||||
UiSwitchModule,
|
||||
UploadModule,
|
||||
],
|
||||
declarations: [
|
||||
|
@@ -108,7 +108,6 @@ export class BitstreamDownloadPageComponent implements OnInit {
|
||||
|
||||
signpostingLinks.forEach((link: SignpostingLink) => {
|
||||
links = links + (isNotEmpty(links) ? ', ' : '') + `<${link.href}> ; rel="${link.rel}"` + (isNotEmpty(link.type) ? ` ; type="${link.type}" ` : ' ');
|
||||
links = links + (isNotEmpty(links) ? ', ' : '') + `<${link.href}> ; rel="${link.rel}" ; type="${link.type}" `;
|
||||
});
|
||||
|
||||
this.responseService.setHeader('Link', links);
|
||||
|
@@ -12,7 +12,7 @@ import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||
* Requesting them as embeds will limit the number of requests
|
||||
*/
|
||||
export const BITSTREAM_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Bitstream>[] = [
|
||||
followLink('bundle', {}, followLink('item')),
|
||||
followLink('bundle', {}, followLink('primaryBitstream'), followLink('item')),
|
||||
followLink('format')
|
||||
];
|
||||
|
||||
|
@@ -24,6 +24,7 @@ import { createPaginatedList } from '../../shared/testing/utils.test';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { MetadataValueFilter } from '../../core/shared/metadata.models';
|
||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||
import { PrimaryBitstreamService } from '../../core/data/primary-bitstream.service';
|
||||
|
||||
const infoNotification: INotification = new Notification('id', NotificationType.Info, 'info');
|
||||
const warningNotification: INotification = new Notification('id', NotificationType.Warning, 'warning');
|
||||
@@ -32,19 +33,27 @@ const successNotification: INotification = new Notification('id', NotificationTy
|
||||
let notificationsService: NotificationsService;
|
||||
let formService: DynamicFormService;
|
||||
let bitstreamService: BitstreamDataService;
|
||||
let primaryBitstreamService: PrimaryBitstreamService;
|
||||
let bitstreamFormatService: BitstreamFormatDataService;
|
||||
let dsoNameService: DSONameService;
|
||||
let bitstream: Bitstream;
|
||||
let bitstreamID: string;
|
||||
let selectedFormat: BitstreamFormat;
|
||||
let allFormats: BitstreamFormat[];
|
||||
let router: Router;
|
||||
|
||||
let currentPrimary: string;
|
||||
let differentPrimary: string;
|
||||
let bundle;
|
||||
let comp: EditBitstreamPageComponent;
|
||||
let fixture: ComponentFixture<EditBitstreamPageComponent>;
|
||||
|
||||
describe('EditBitstreamPageComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
bitstreamID = 'current-bitstream-id';
|
||||
currentPrimary = bitstreamID;
|
||||
differentPrimary = '12345-abcde-54321-edcba';
|
||||
|
||||
allFormats = [
|
||||
Object.assign({
|
||||
id: '1',
|
||||
@@ -53,7 +62,7 @@ describe('EditBitstreamPageComponent', () => {
|
||||
supportLevel: BitstreamFormatSupportLevel.Unknown,
|
||||
mimetype: 'application/octet-stream',
|
||||
_links: {
|
||||
self: {href: 'format-selflink-1'}
|
||||
self: { href: 'format-selflink-1' }
|
||||
}
|
||||
}),
|
||||
Object.assign({
|
||||
@@ -63,7 +72,7 @@ describe('EditBitstreamPageComponent', () => {
|
||||
supportLevel: BitstreamFormatSupportLevel.Known,
|
||||
mimetype: 'image/png',
|
||||
_links: {
|
||||
self: {href: 'format-selflink-2'}
|
||||
self: { href: 'format-selflink-2' }
|
||||
}
|
||||
}),
|
||||
Object.assign({
|
||||
@@ -73,7 +82,7 @@ describe('EditBitstreamPageComponent', () => {
|
||||
supportLevel: BitstreamFormatSupportLevel.Known,
|
||||
mimetype: 'image/gif',
|
||||
_links: {
|
||||
self: {href: 'format-selflink-3'}
|
||||
self: { href: 'format-selflink-3' }
|
||||
}
|
||||
})
|
||||
] as BitstreamFormat[];
|
||||
@@ -103,15 +112,52 @@ describe('EditBitstreamPageComponent', () => {
|
||||
success: successNotification
|
||||
}
|
||||
);
|
||||
|
||||
bundle = {
|
||||
_links: {
|
||||
primaryBitstream: {
|
||||
href: 'bundle-selflink'
|
||||
}
|
||||
},
|
||||
item: createSuccessfulRemoteDataObject$(Object.assign(new Item(), {
|
||||
uuid: 'some-uuid',
|
||||
firstMetadataValue(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): string {
|
||||
return undefined;
|
||||
},
|
||||
}))
|
||||
};
|
||||
|
||||
const result = createSuccessfulRemoteDataObject$(bundle);
|
||||
primaryBitstreamService = jasmine.createSpyObj('PrimaryBitstreamService',
|
||||
{
|
||||
put: result,
|
||||
create: result,
|
||||
delete: result,
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('EditBitstreamPageComponent no IIIF fields', () => {
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
|
||||
bundle = {
|
||||
_links: {
|
||||
primaryBitstream: {
|
||||
href: 'bundle-selflink'
|
||||
}
|
||||
},
|
||||
item: createSuccessfulRemoteDataObject$(Object.assign(new Item(), {
|
||||
uuid: 'some-uuid',
|
||||
firstMetadataValue(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): string {
|
||||
return undefined;
|
||||
},
|
||||
}))
|
||||
};
|
||||
const bundleName = 'ORIGINAL';
|
||||
|
||||
bitstream = Object.assign(new Bitstream(), {
|
||||
uuid: bitstreamID,
|
||||
id: bitstreamID,
|
||||
metadata: {
|
||||
'dc.description': [
|
||||
{
|
||||
@@ -128,17 +174,11 @@ describe('EditBitstreamPageComponent', () => {
|
||||
_links: {
|
||||
self: 'bitstream-selflink'
|
||||
},
|
||||
bundle: createSuccessfulRemoteDataObject$({
|
||||
item: createSuccessfulRemoteDataObject$(Object.assign(new Item(), {
|
||||
uuid: 'some-uuid',
|
||||
firstMetadataValue(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): string {
|
||||
return undefined;
|
||||
},
|
||||
}))
|
||||
})
|
||||
bundle: createSuccessfulRemoteDataObject$(bundle)
|
||||
});
|
||||
bitstreamService = jasmine.createSpyObj('bitstreamService', {
|
||||
findById: createSuccessfulRemoteDataObject$(bitstream),
|
||||
findByHref: createSuccessfulRemoteDataObject$(bitstream),
|
||||
update: createSuccessfulRemoteDataObject$(bitstream),
|
||||
updateFormat: createSuccessfulRemoteDataObject$(bitstream),
|
||||
commitUpdates: {},
|
||||
@@ -155,17 +195,19 @@ describe('EditBitstreamPageComponent', () => {
|
||||
imports: [TranslateModule.forRoot(), RouterTestingModule],
|
||||
declarations: [EditBitstreamPageComponent, FileSizePipe, VarDirective],
|
||||
providers: [
|
||||
{provide: NotificationsService, useValue: notificationsService},
|
||||
{provide: DynamicFormService, useValue: formService},
|
||||
{provide: ActivatedRoute,
|
||||
{ provide: NotificationsService, useValue: notificationsService },
|
||||
{ provide: DynamicFormService, useValue: formService },
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: {
|
||||
data: observableOf({bitstream: createSuccessfulRemoteDataObject(bitstream)}),
|
||||
snapshot: {queryParams: {}}
|
||||
data: observableOf({ bitstream: createSuccessfulRemoteDataObject(bitstream) }),
|
||||
snapshot: { queryParams: {} }
|
||||
}
|
||||
},
|
||||
{provide: BitstreamDataService, useValue: bitstreamService},
|
||||
{provide: DSONameService, useValue: dsoNameService},
|
||||
{provide: BitstreamFormatDataService, useValue: bitstreamFormatService},
|
||||
{ provide: BitstreamDataService, useValue: bitstreamService },
|
||||
{ provide: DSONameService, useValue: dsoNameService },
|
||||
{ provide: BitstreamFormatDataService, useValue: bitstreamFormatService },
|
||||
{ provide: PrimaryBitstreamService, useValue: primaryBitstreamService },
|
||||
ChangeDetectorRef
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
@@ -203,6 +245,27 @@ describe('EditBitstreamPageComponent', () => {
|
||||
it('should put the \"New Format\" input on invisible', () => {
|
||||
expect(comp.formLayout.newFormat.grid.host).toContain('invisible');
|
||||
});
|
||||
describe('when the bitstream is the primary bitstream on the bundle', () => {
|
||||
beforeEach(() => {
|
||||
(comp as any).primaryBitstreamUUID = currentPrimary;
|
||||
comp.setForm();
|
||||
rawForm = comp.formGroup.getRawValue();
|
||||
|
||||
});
|
||||
it('should enable the primary bitstream toggle', () => {
|
||||
expect(rawForm.fileNamePrimaryContainer.primaryBitstream).toEqual(true);
|
||||
});
|
||||
});
|
||||
describe('when the bitstream is not the primary bitstream on the bundle', () => {
|
||||
beforeEach(() => {
|
||||
(comp as any).primaryBitstreamUUID = differentPrimary;
|
||||
comp.setForm();
|
||||
rawForm = comp.formGroup.getRawValue();
|
||||
});
|
||||
it('should disable the primary bitstream toggle', () => {
|
||||
expect(rawForm.fileNamePrimaryContainer.primaryBitstream).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when an unknown format is selected', () => {
|
||||
@@ -216,6 +279,83 @@ describe('EditBitstreamPageComponent', () => {
|
||||
});
|
||||
|
||||
describe('onSubmit', () => {
|
||||
describe('when the primaryBitstream changed', () => {
|
||||
describe('to the current bitstream', () => {
|
||||
beforeEach(() => {
|
||||
const rawValue = Object.assign(comp.formGroup.getRawValue(), { fileNamePrimaryContainer: { primaryBitstream: true } });
|
||||
spyOn(comp.formGroup, 'getRawValue').and.returnValue(rawValue);
|
||||
});
|
||||
|
||||
describe('from a different primary bitstream', () => {
|
||||
beforeEach(() => {
|
||||
(comp as any).primaryBitstreamUUID = differentPrimary;
|
||||
comp.onSubmit();
|
||||
});
|
||||
|
||||
it('should call put with the correct bitstream on the PrimaryBitstreamService', () => {
|
||||
expect(primaryBitstreamService.put).toHaveBeenCalledWith(jasmine.objectContaining({uuid: currentPrimary}), bundle);
|
||||
});
|
||||
});
|
||||
|
||||
describe('from no primary bitstream', () => {
|
||||
beforeEach(() => {
|
||||
(comp as any).primaryBitstreamUUID = null;
|
||||
comp.onSubmit();
|
||||
});
|
||||
|
||||
it('should call create with the correct bitstream on the PrimaryBitstreamService', () => {
|
||||
expect(primaryBitstreamService.create).toHaveBeenCalledWith(jasmine.objectContaining({uuid: currentPrimary}), bundle);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('to no primary bitstream', () => {
|
||||
beforeEach(() => {
|
||||
const rawValue = Object.assign(comp.formGroup.getRawValue(), { fileNamePrimaryContainer: { primaryBitstream: false } });
|
||||
spyOn(comp.formGroup, 'getRawValue').and.returnValue(rawValue);
|
||||
});
|
||||
|
||||
describe('from the current bitstream', () => {
|
||||
beforeEach(() => {
|
||||
(comp as any).primaryBitstreamUUID = currentPrimary;
|
||||
comp.onSubmit();
|
||||
});
|
||||
|
||||
it('should call delete on the PrimaryBitstreamService', () => {
|
||||
expect(primaryBitstreamService.delete).toHaveBeenCalledWith(jasmine.objectContaining(bundle));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('when the primaryBitstream did not change', () => {
|
||||
describe('the current bitstream stayed the primary bitstream', () => {
|
||||
beforeEach(() => {
|
||||
const rawValue = Object.assign(comp.formGroup.getRawValue(), { fileNamePrimaryContainer: { primaryBitstream: true } });
|
||||
spyOn(comp.formGroup, 'getRawValue').and.returnValue(rawValue);
|
||||
(comp as any).primaryBitstreamUUID = currentPrimary;
|
||||
comp.onSubmit();
|
||||
});
|
||||
it('should not call anything on the PrimaryBitstreamService', () => {
|
||||
expect(primaryBitstreamService.put).not.toHaveBeenCalled();
|
||||
expect(primaryBitstreamService.delete).not.toHaveBeenCalled();
|
||||
expect(primaryBitstreamService.create).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('the bitstream was not and did not become the primary bitstream', () => {
|
||||
beforeEach(() => {
|
||||
const rawValue = Object.assign(comp.formGroup.getRawValue(), { fileNamePrimaryContainer: { primaryBitstream: false } });
|
||||
spyOn(comp.formGroup, 'getRawValue').and.returnValue(rawValue);
|
||||
(comp as any).primaryBitstreamUUID = differentPrimary;
|
||||
comp.onSubmit();
|
||||
});
|
||||
it('should not call anything on the PrimaryBitstreamService', () => {
|
||||
expect(primaryBitstreamService.put).not.toHaveBeenCalled();
|
||||
expect(primaryBitstreamService.delete).not.toHaveBeenCalled();
|
||||
expect(primaryBitstreamService.create).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when selected format hasn\'t changed', () => {
|
||||
beforeEach(() => {
|
||||
comp.onSubmit();
|
||||
@@ -261,20 +401,13 @@ describe('EditBitstreamPageComponent', () => {
|
||||
expect(comp.navigateToItemEditBitstreams).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
describe('when navigateToItemEditBitstreams is called, and the component has an itemId', () => {
|
||||
describe('when navigateToItemEditBitstreams is called', () => {
|
||||
it('should redirect to the item edit page on the bitstreams tab with the itemId from the component', () => {
|
||||
comp.itemId = 'some-uuid1';
|
||||
comp.navigateToItemEditBitstreams();
|
||||
expect(router.navigate).toHaveBeenCalledWith([getEntityEditRoute(null, 'some-uuid1'), 'bitstreams']);
|
||||
});
|
||||
});
|
||||
describe('when navigateToItemEditBitstreams is called, and the component does not have an itemId', () => {
|
||||
it('should redirect to the item edit page on the bitstreams tab with the itemId from the bundle links ', () => {
|
||||
comp.itemId = undefined;
|
||||
comp.navigateToItemEditBitstreams();
|
||||
expect(router.navigate).toHaveBeenCalledWith([getEntityEditRoute(null, 'some-uuid'), 'bitstreams']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('EditBitstreamPageComponent with IIIF fields', () => {
|
||||
@@ -321,16 +454,22 @@ describe('EditBitstreamPageComponent', () => {
|
||||
self: 'bitstream-selflink'
|
||||
},
|
||||
bundle: createSuccessfulRemoteDataObject$({
|
||||
_links: {
|
||||
primaryBitstream: {
|
||||
href: 'bundle-selflink'
|
||||
}
|
||||
},
|
||||
item: createSuccessfulRemoteDataObject$(Object.assign(new Item(), {
|
||||
uuid: 'some-uuid',
|
||||
firstMetadataValue(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): string {
|
||||
return 'True';
|
||||
}
|
||||
}))
|
||||
})
|
||||
}),
|
||||
});
|
||||
bitstreamService = jasmine.createSpyObj('bitstreamService', {
|
||||
findById: createSuccessfulRemoteDataObject$(bitstream),
|
||||
findByHref: createSuccessfulRemoteDataObject$(bitstream),
|
||||
update: createSuccessfulRemoteDataObject$(bitstream),
|
||||
updateFormat: createSuccessfulRemoteDataObject$(bitstream),
|
||||
commitUpdates: {},
|
||||
@@ -357,6 +496,7 @@ describe('EditBitstreamPageComponent', () => {
|
||||
{provide: BitstreamDataService, useValue: bitstreamService},
|
||||
{provide: DSONameService, useValue: dsoNameService},
|
||||
{provide: BitstreamFormatDataService, useValue: bitstreamFormatService},
|
||||
{ provide: PrimaryBitstreamService, useValue: primaryBitstreamService },
|
||||
ChangeDetectorRef
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
@@ -371,7 +511,6 @@ describe('EditBitstreamPageComponent', () => {
|
||||
spyOn(router, 'navigate');
|
||||
});
|
||||
|
||||
|
||||
describe('on startup', () => {
|
||||
let rawForm;
|
||||
|
||||
@@ -440,16 +579,22 @@ describe('EditBitstreamPageComponent', () => {
|
||||
self: 'bitstream-selflink'
|
||||
},
|
||||
bundle: createSuccessfulRemoteDataObject$({
|
||||
_links: {
|
||||
primaryBitstream: {
|
||||
href: 'bundle-selflink'
|
||||
}
|
||||
},
|
||||
item: createSuccessfulRemoteDataObject$(Object.assign(new Item(), {
|
||||
uuid: 'some-uuid',
|
||||
firstMetadataValue(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): string {
|
||||
return 'True';
|
||||
}
|
||||
}))
|
||||
})
|
||||
}),
|
||||
});
|
||||
bitstreamService = jasmine.createSpyObj('bitstreamService', {
|
||||
findById: createSuccessfulRemoteDataObject$(bitstream),
|
||||
findByHref: createSuccessfulRemoteDataObject$(bitstream),
|
||||
update: createSuccessfulRemoteDataObject$(bitstream),
|
||||
updateFormat: createSuccessfulRemoteDataObject$(bitstream),
|
||||
commitUpdates: {},
|
||||
@@ -475,6 +620,7 @@ describe('EditBitstreamPageComponent', () => {
|
||||
{provide: BitstreamDataService, useValue: bitstreamService},
|
||||
{provide: DSONameService, useValue: dsoNameService},
|
||||
{provide: BitstreamFormatDataService, useValue: bitstreamFormatService},
|
||||
{ provide: PrimaryBitstreamService, useValue: primaryBitstreamService },
|
||||
ChangeDetectorRef
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
@@ -496,7 +642,7 @@ describe('EditBitstreamPageComponent', () => {
|
||||
rawForm = comp.formGroup.getRawValue();
|
||||
});
|
||||
|
||||
it('should NOT set isIIIF to true', () => {
|
||||
it('should NOT set is IIIF to true', () => {
|
||||
expect(comp.isIIIF).toBeFalse();
|
||||
});
|
||||
it('should put the \"IIIF Label\" input not to be shown', () => {
|
||||
|
@@ -1,57 +1,31 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
OnDestroy,
|
||||
OnInit
|
||||
} from '@angular/core';
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Bitstream } from '../../core/shared/bitstream.model';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { map, mergeMap, switchMap } from 'rxjs/operators';
|
||||
import {
|
||||
combineLatest,
|
||||
combineLatest as observableCombineLatest,
|
||||
Observable,
|
||||
of as observableOf,
|
||||
Subscription
|
||||
} from 'rxjs';
|
||||
import {
|
||||
DynamicFormControlModel,
|
||||
DynamicFormGroupModel,
|
||||
DynamicFormLayout,
|
||||
DynamicFormService,
|
||||
DynamicInputModel,
|
||||
DynamicSelectModel
|
||||
} from '@ng-dynamic-forms/core';
|
||||
import { filter, map, switchMap, tap } from 'rxjs/operators';
|
||||
import { combineLatest, combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs';
|
||||
import { DynamicFormControlModel, DynamicFormGroupModel, DynamicFormLayout, DynamicFormService, DynamicInputModel, DynamicSelectModel } from '@ng-dynamic-forms/core';
|
||||
import { UntypedFormGroup } from '@angular/forms';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { DynamicCustomSwitchModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
|
||||
import {
|
||||
getAllSucceededRemoteDataPayload,
|
||||
getFirstCompletedRemoteData,
|
||||
getFirstSucceededRemoteData,
|
||||
getFirstSucceededRemoteDataPayload,
|
||||
getRemoteDataPayload
|
||||
} from '../../core/shared/operators';
|
||||
import { getAllSucceededRemoteDataPayload, getFirstCompletedRemoteData, getFirstSucceededRemoteData, getFirstSucceededRemoteDataPayload, getRemoteDataPayload } from '../../core/shared/operators';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { BitstreamFormatDataService } from '../../core/data/bitstream-format-data.service';
|
||||
import { BitstreamFormat } from '../../core/shared/bitstream-format.model';
|
||||
import { BitstreamFormatSupportLevel } from '../../core/shared/bitstream-format-support-level';
|
||||
import { hasValue, isNotEmpty, isEmpty } from '../../shared/empty.util';
|
||||
import { hasValue, hasValueOperator, isEmpty, isNotEmpty } from '../../shared/empty.util';
|
||||
import { Metadata } from '../../core/shared/metadata.utils';
|
||||
import { Location } from '@angular/common';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
||||
import { getEntityEditRoute, getItemEditRoute } from '../../item-page/item-page-routing-paths';
|
||||
import { getEntityEditRoute } from '../../item-page/item-page-routing-paths';
|
||||
import { Bundle } from '../../core/shared/bundle.model';
|
||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import {
|
||||
DsDynamicInputModel
|
||||
} from '../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model';
|
||||
import { DsDynamicInputModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model';
|
||||
import { DsDynamicTextAreaModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-textarea.model';
|
||||
import { PrimaryBitstreamService } from '../../core/data/primary-bitstream.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-edit-bitstream-page',
|
||||
@@ -76,6 +50,11 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
bitstreamFormatsRD$: Observable<RemoteData<PaginatedList<BitstreamFormat>>>;
|
||||
|
||||
/**
|
||||
* The UUID of the primary bitstream for this bundle
|
||||
*/
|
||||
primaryBitstreamUUID: string;
|
||||
|
||||
/**
|
||||
* The bitstream to edit
|
||||
*/
|
||||
@@ -191,19 +170,19 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
||||
* The Dynamic Input Model for the iiif label
|
||||
*/
|
||||
iiifLabelModel = new DsDynamicInputModel({
|
||||
hasSelectableMetadata: false, metadataFields: [], repeatable: false, submissionId: '',
|
||||
id: 'iiifLabel',
|
||||
name: 'iiifLabel'
|
||||
},
|
||||
hasSelectableMetadata: false, metadataFields: [], repeatable: false, submissionId: '',
|
||||
id: 'iiifLabel',
|
||||
name: 'iiifLabel'
|
||||
},
|
||||
{
|
||||
grid: {
|
||||
host: 'col col-lg-6 d-inline-block'
|
||||
}
|
||||
grid: {
|
||||
host: 'col col-lg-6 d-inline-block'
|
||||
}
|
||||
});
|
||||
iiifLabelContainer = new DynamicFormGroupModel({
|
||||
id: 'iiifLabelContainer',
|
||||
group: [this.iiifLabelModel]
|
||||
},{
|
||||
}, {
|
||||
grid: {
|
||||
host: 'form-row'
|
||||
}
|
||||
@@ -213,7 +192,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
||||
hasSelectableMetadata: false, metadataFields: [], repeatable: false, submissionId: '',
|
||||
id: 'iiifToc',
|
||||
name: 'iiifToc',
|
||||
},{
|
||||
}, {
|
||||
grid: {
|
||||
host: 'col col-lg-6 d-inline-block'
|
||||
}
|
||||
@@ -221,7 +200,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
||||
iiifTocContainer = new DynamicFormGroupModel({
|
||||
id: 'iiifTocContainer',
|
||||
group: [this.iiifTocModel]
|
||||
},{
|
||||
}, {
|
||||
grid: {
|
||||
host: 'form-row'
|
||||
}
|
||||
@@ -231,7 +210,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
||||
hasSelectableMetadata: false, metadataFields: [], repeatable: false, submissionId: '',
|
||||
id: 'iiifWidth',
|
||||
name: 'iiifWidth',
|
||||
},{
|
||||
}, {
|
||||
grid: {
|
||||
host: 'col col-lg-6 d-inline-block'
|
||||
}
|
||||
@@ -239,7 +218,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
||||
iiifWidthContainer = new DynamicFormGroupModel({
|
||||
id: 'iiifWidthContainer',
|
||||
group: [this.iiifWidthModel]
|
||||
},{
|
||||
}, {
|
||||
grid: {
|
||||
host: 'form-row'
|
||||
}
|
||||
@@ -249,7 +228,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
||||
hasSelectableMetadata: false, metadataFields: [], repeatable: false, submissionId: '',
|
||||
id: 'iiifHeight',
|
||||
name: 'iiifHeight'
|
||||
},{
|
||||
}, {
|
||||
grid: {
|
||||
host: 'col col-lg-6 d-inline-block'
|
||||
}
|
||||
@@ -257,7 +236,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
||||
iiifHeightContainer = new DynamicFormGroupModel({
|
||||
id: 'iiifHeightContainer',
|
||||
group: [this.iiifHeightModel]
|
||||
},{
|
||||
}, {
|
||||
grid: {
|
||||
host: 'form-row'
|
||||
}
|
||||
@@ -280,11 +259,11 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
||||
this.fileNameModel,
|
||||
this.primaryBitstreamModel
|
||||
]
|
||||
},{
|
||||
grid: {
|
||||
host: 'form-row'
|
||||
}
|
||||
}),
|
||||
}, {
|
||||
grid: {
|
||||
host: 'form-row'
|
||||
}
|
||||
}),
|
||||
new DynamicFormGroupModel({
|
||||
id: 'descriptionContainer',
|
||||
group: [
|
||||
@@ -316,7 +295,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
||||
},
|
||||
primaryBitstream: {
|
||||
grid: {
|
||||
host: 'col col-sm-4 d-inline-block switch'
|
||||
host: 'col col-sm-4 d-inline-block switch border-0'
|
||||
}
|
||||
},
|
||||
description: {
|
||||
@@ -380,13 +359,17 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
isIIIF = false;
|
||||
|
||||
|
||||
/**
|
||||
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||
* @type {Array}
|
||||
*/
|
||||
protected subs: Subscription[] = [];
|
||||
|
||||
/**
|
||||
* The parent bundle containing the Bitstream
|
||||
* @private
|
||||
*/
|
||||
private bundle: Bundle;
|
||||
|
||||
constructor(private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
@@ -397,7 +380,9 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
||||
private bitstreamService: BitstreamDataService,
|
||||
public dsoNameService: DSONameService,
|
||||
private notificationsService: NotificationsService,
|
||||
private bitstreamFormatService: BitstreamFormatDataService) {
|
||||
private bitstreamFormatService: BitstreamFormatDataService,
|
||||
private primaryBitstreamService: PrimaryBitstreamService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -410,35 +395,59 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.itemId = this.route.snapshot.queryParams.itemId;
|
||||
this.entityType = this.route.snapshot.queryParams.entityType;
|
||||
this.bitstreamRD$ = this.route.data.pipe(map((data) => data.bitstream));
|
||||
this.bitstreamRD$ = this.route.data.pipe(map((data: any) => data.bitstream));
|
||||
this.bitstreamFormatsRD$ = this.bitstreamFormatService.findAll(this.findAllOptions);
|
||||
|
||||
const bitstream$ = this.bitstreamRD$.pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
getRemoteDataPayload()
|
||||
getRemoteDataPayload(),
|
||||
);
|
||||
|
||||
const allFormats$ = this.bitstreamFormatsRD$.pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
getRemoteDataPayload()
|
||||
getRemoteDataPayload(),
|
||||
);
|
||||
|
||||
const bundle$ = bitstream$.pipe(
|
||||
switchMap((bitstream: Bitstream) => bitstream.bundle),
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
);
|
||||
|
||||
const primaryBitstream$ = bundle$.pipe(
|
||||
hasValueOperator(),
|
||||
switchMap((bundle: Bundle) => this.bitstreamService.findByHref(bundle._links.primaryBitstream.href)),
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
);
|
||||
|
||||
const item$ = bundle$.pipe(
|
||||
switchMap((bundle: Bundle) => bundle.item),
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
);
|
||||
this.subs.push(
|
||||
observableCombineLatest(
|
||||
bitstream$,
|
||||
allFormats$
|
||||
).subscribe(([bitstream, allFormats]) => {
|
||||
this.bitstream = bitstream as Bitstream;
|
||||
this.formats = allFormats.page;
|
||||
this.setIiifStatus(this.bitstream);
|
||||
})
|
||||
allFormats$,
|
||||
bundle$,
|
||||
primaryBitstream$,
|
||||
item$,
|
||||
).pipe()
|
||||
.subscribe(([bitstream, allFormats, bundle, primaryBitstream, item]) => {
|
||||
this.bitstream = bitstream as Bitstream;
|
||||
this.formats = allFormats.page;
|
||||
this.bundle = bundle;
|
||||
// hasValue(primaryBitstream) because if there's no primaryBitstream on the bundle it will
|
||||
// be a success response, but empty
|
||||
this.primaryBitstreamUUID = hasValue(primaryBitstream) ? primaryBitstream.uuid : null;
|
||||
this.itemId = item.uuid;
|
||||
this.setIiifStatus(this.bitstream);
|
||||
})
|
||||
);
|
||||
|
||||
this.subs.push(
|
||||
this.translate.onLangChange
|
||||
.subscribe(() => {
|
||||
this.updateFieldTranslations();
|
||||
})
|
||||
this.updateFieldTranslations();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -460,7 +469,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
||||
this.formGroup.patchValue({
|
||||
fileNamePrimaryContainer: {
|
||||
fileName: bitstream.name,
|
||||
primaryBitstream: false
|
||||
primaryBitstream: this.primaryBitstreamUUID === bitstream.uuid
|
||||
},
|
||||
descriptionContainer: {
|
||||
description: bitstream.firstMetadataValue('dc.description')
|
||||
@@ -571,9 +580,56 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
||||
const updatedBitstream = this.formToBitstream(updatedValues);
|
||||
const selectedFormat = this.formats.find((f: BitstreamFormat) => f.id === updatedValues.formatContainer.selectedFormat);
|
||||
const isNewFormat = selectedFormat.id !== this.originalFormat.id;
|
||||
const isPrimary = updatedValues.fileNamePrimaryContainer.primaryBitstream;
|
||||
const wasPrimary = this.primaryBitstreamUUID === this.bitstream.uuid;
|
||||
|
||||
let bitstream$;
|
||||
let bundle$: Observable<Bundle>;
|
||||
let errorWhileSaving = false;
|
||||
|
||||
if (wasPrimary !== isPrimary) {
|
||||
let bundleRd$: Observable<RemoteData<Bundle>>;
|
||||
if (wasPrimary) {
|
||||
bundleRd$ = this.primaryBitstreamService.delete(this.bundle);
|
||||
} else if (hasValue(this.primaryBitstreamUUID)) {
|
||||
bundleRd$ = this.primaryBitstreamService.put(this.bitstream, this.bundle);
|
||||
} else {
|
||||
bundleRd$ = this.primaryBitstreamService.create(this.bitstream, this.bundle);
|
||||
}
|
||||
|
||||
const completedBundleRd$ = bundleRd$.pipe(getFirstCompletedRemoteData());
|
||||
|
||||
this.subs.push(completedBundleRd$.pipe(
|
||||
filter((bundleRd: RemoteData<Bundle>) => bundleRd.hasFailed)
|
||||
).subscribe((bundleRd: RemoteData<Bundle>) => {
|
||||
this.notificationsService.error(
|
||||
this.translate.instant(this.NOTIFICATIONS_PREFIX + 'error.primaryBitstream.title'),
|
||||
bundleRd.errorMessage
|
||||
);
|
||||
errorWhileSaving = true;
|
||||
}));
|
||||
|
||||
bundle$ = completedBundleRd$.pipe(
|
||||
map((bundleRd: RemoteData<Bundle>) => {
|
||||
if (bundleRd.hasSucceeded) {
|
||||
return bundleRd.payload;
|
||||
} else {
|
||||
return this.bundle;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.subs.push(bundle$.pipe(
|
||||
hasValueOperator(),
|
||||
switchMap((bundle: Bundle) => this.bitstreamService.findByHref(bundle._links.primaryBitstream.href, false)),
|
||||
getFirstSucceededRemoteDataPayload()
|
||||
).subscribe((bitstream: Bitstream) => {
|
||||
this.primaryBitstreamUUID = hasValue(bitstream) ? bitstream.uuid : null;
|
||||
}));
|
||||
|
||||
} else {
|
||||
bundle$ = observableOf(this.bundle);
|
||||
}
|
||||
if (isNewFormat) {
|
||||
bitstream$ = this.bitstreamService.updateFormat(this.bitstream, selectedFormat).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
@@ -592,7 +648,8 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
||||
bitstream$ = observableOf(this.bitstream);
|
||||
}
|
||||
|
||||
bitstream$.pipe(
|
||||
combineLatest([bundle$, bitstream$]).pipe(
|
||||
tap(([bundle]) => this.bundle = bundle),
|
||||
switchMap(() => {
|
||||
return this.bitstreamService.update(updatedBitstream).pipe(
|
||||
getFirstSucceededRemoteDataPayload()
|
||||
@@ -604,7 +661,9 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
||||
this.translate.instant(this.NOTIFICATIONS_PREFIX + 'saved.title'),
|
||||
this.translate.instant(this.NOTIFICATIONS_PREFIX + 'saved.content')
|
||||
);
|
||||
this.navigateToItemEditBitstreams();
|
||||
if (!errorWhileSaving) {
|
||||
this.navigateToItemEditBitstreams();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -615,8 +674,6 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
||||
formToBitstream(rawForm): Bitstream {
|
||||
const updatedBitstream = cloneDeep(this.bitstream);
|
||||
const newMetadata = updatedBitstream.metadata;
|
||||
// TODO: Set bitstream to primary when supported
|
||||
const primary = rawForm.fileNamePrimaryContainer.primaryBitstream;
|
||||
Metadata.setFirstValue(newMetadata, 'dc.title', rawForm.fileNamePrimaryContainer.fileName);
|
||||
if (isEmpty(rawForm.descriptionContainer.description)) {
|
||||
delete newMetadata['dc.description'];
|
||||
@@ -633,11 +690,11 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
||||
} else {
|
||||
Metadata.setFirstValue(newMetadata, this.IIIF_LABEL_METADATA, rawForm.iiifLabelContainer.iiifLabel);
|
||||
}
|
||||
if (isEmpty(rawForm.iiifTocContainer.iiifToc)) {
|
||||
delete newMetadata[this.IIIF_TOC_METADATA];
|
||||
} else {
|
||||
if (isEmpty(rawForm.iiifTocContainer.iiifToc)) {
|
||||
delete newMetadata[this.IIIF_TOC_METADATA];
|
||||
} else {
|
||||
Metadata.setFirstValue(newMetadata, this.IIIF_TOC_METADATA, rawForm.iiifTocContainer.iiifToc);
|
||||
}
|
||||
}
|
||||
if (isEmpty(rawForm.iiifWidthContainer.iiifWidth)) {
|
||||
delete newMetadata[this.IMAGE_WIDTH_METADATA];
|
||||
} else {
|
||||
@@ -668,15 +725,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
||||
* otherwise retrieve the item ID based on the owning bundle's link
|
||||
*/
|
||||
navigateToItemEditBitstreams() {
|
||||
if (hasValue(this.itemId)) {
|
||||
this.router.navigate([getEntityEditRoute(this.entityType, this.itemId), 'bitstreams']);
|
||||
} else {
|
||||
this.bitstream.bundle.pipe(getFirstSucceededRemoteDataPayload(),
|
||||
mergeMap((bundle: Bundle) => bundle.item.pipe(getFirstSucceededRemoteDataPayload())))
|
||||
.subscribe((item) => {
|
||||
this.router.navigate(([getItemEditRoute(item), 'bitstreams']));
|
||||
});
|
||||
}
|
||||
this.router.navigate([getEntityEditRoute(this.entityType, this.itemId), 'bitstreams']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -701,11 +750,11 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
||||
const isEnabled$ = this.bitstream.bundle.pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
map((bundle: RemoteData<Bundle>) => bundle.payload.item.pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
map((item: RemoteData<Item>) =>
|
||||
(item.payload.firstMetadataValue('dspace.iiif.enabled') &&
|
||||
item.payload.firstMetadataValue('dspace.iiif.enabled').match(regexIIIFItem) !== null)
|
||||
))));
|
||||
getFirstSucceededRemoteData(),
|
||||
map((item: RemoteData<Item>) =>
|
||||
(item.payload.firstMetadataValue('dspace.iiif.enabled') &&
|
||||
item.payload.firstMetadataValue('dspace.iiif.enabled').match(regexIIIFItem) !== null)
|
||||
))));
|
||||
|
||||
const iiifSub = combineLatest(
|
||||
isImage$,
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import { first } from 'rxjs/operators';
|
||||
import { BrowseByGuard } from './browse-by-guard';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
||||
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
||||
import { BrowseByDataType } from './browse-by-switcher/browse-by-decorator';
|
||||
import { ValueListBrowseDefinition } from '../core/shared/value-list-browse-definition.model';
|
||||
import { DSONameServiceMock } from '../shared/mocks/dso-name.service.mock';
|
||||
import { DSONameService } from '../core/breadcrumbs/dso-name.service';
|
||||
import { RouterStub } from '../shared/testing/router.stub';
|
||||
|
||||
describe('BrowseByGuard', () => {
|
||||
describe('canActivate', () => {
|
||||
@@ -13,6 +14,7 @@ describe('BrowseByGuard', () => {
|
||||
let dsoService: any;
|
||||
let translateService: any;
|
||||
let browseDefinitionService: any;
|
||||
let router: any;
|
||||
|
||||
const name = 'An interesting DSO';
|
||||
const title = 'Author';
|
||||
@@ -35,7 +37,9 @@ describe('BrowseByGuard', () => {
|
||||
findById: () => createSuccessfulRemoteDataObject$(browseDefinition)
|
||||
};
|
||||
|
||||
guard = new BrowseByGuard(dsoService, translateService, browseDefinitionService, new DSONameServiceMock() as DSONameService);
|
||||
router = new RouterStub() as any;
|
||||
|
||||
guard = new BrowseByGuard(dsoService, translateService, browseDefinitionService, new DSONameServiceMock() as DSONameService, router);
|
||||
});
|
||||
|
||||
it('should return true, and sets up the data correctly, with a scope and value', () => {
|
||||
@@ -65,6 +69,7 @@ describe('BrowseByGuard', () => {
|
||||
value: '"' + value + '"'
|
||||
};
|
||||
expect(scopedRoute.data).toEqual(result);
|
||||
expect(router.navigate).not.toHaveBeenCalled();
|
||||
expect(canActivate).toEqual(true);
|
||||
}
|
||||
);
|
||||
@@ -97,6 +102,7 @@ describe('BrowseByGuard', () => {
|
||||
value: ''
|
||||
};
|
||||
expect(scopedNoValueRoute.data).toEqual(result);
|
||||
expect(router.navigate).not.toHaveBeenCalled();
|
||||
expect(canActivate).toEqual(true);
|
||||
}
|
||||
);
|
||||
@@ -128,9 +134,33 @@ describe('BrowseByGuard', () => {
|
||||
value: '"' + value + '"'
|
||||
};
|
||||
expect(route.data).toEqual(result);
|
||||
expect(router.navigate).not.toHaveBeenCalled();
|
||||
expect(canActivate).toEqual(true);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should return false, and sets up the data correctly, without a scope and with a value', () => {
|
||||
jasmine.getEnv().allowRespy(true);
|
||||
spyOn(browseDefinitionService, 'findById').and.returnValue(createFailedRemoteDataObject$());
|
||||
const scopedRoute = {
|
||||
data: {
|
||||
title: field,
|
||||
},
|
||||
params: {
|
||||
id,
|
||||
},
|
||||
queryParams: {
|
||||
scope,
|
||||
value
|
||||
}
|
||||
};
|
||||
guard.canActivate(scopedRoute as any, undefined)
|
||||
.pipe(first())
|
||||
.subscribe((canActivate) => {
|
||||
expect(router.navigate).toHaveBeenCalled();
|
||||
expect(canActivate).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,15 +1,17 @@
|
||||
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { DSpaceObjectDataService } from '../core/data/dspace-object-data.service';
|
||||
import { hasNoValue, hasValue } from '../shared/empty.util';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
import { getFirstSucceededRemoteDataPayload } from '../core/shared/operators';
|
||||
import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../core/shared/operators';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { BrowseDefinitionDataService } from '../core/browse/browse-definition-data.service';
|
||||
import { BrowseDefinition } from '../core/shared/browse-definition.model';
|
||||
import { DSONameService } from '../core/breadcrumbs/dso-name.service';
|
||||
import { DSpaceObject } from '../core/shared/dspace-object.model';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { PAGE_NOT_FOUND_PATH } from '../app-routing-paths';
|
||||
|
||||
@Injectable()
|
||||
/**
|
||||
@@ -21,15 +23,19 @@ export class BrowseByGuard implements CanActivate {
|
||||
protected translate: TranslateService,
|
||||
protected browseDefinitionService: BrowseDefinitionDataService,
|
||||
protected dsoNameService: DSONameService,
|
||||
protected router: Router,
|
||||
) {
|
||||
}
|
||||
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
|
||||
const title = route.data.title;
|
||||
const id = route.params.id || route.queryParams.id || route.data.id;
|
||||
let browseDefinition$: Observable<BrowseDefinition>;
|
||||
let browseDefinition$: Observable<BrowseDefinition | undefined>;
|
||||
if (hasNoValue(route.data.browseDefinition) && hasValue(id)) {
|
||||
browseDefinition$ = this.browseDefinitionService.findById(id).pipe(getFirstSucceededRemoteDataPayload());
|
||||
browseDefinition$ = this.browseDefinitionService.findById(id).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
map((browseDefinitionRD: RemoteData<BrowseDefinition>) => browseDefinitionRD.payload),
|
||||
);
|
||||
} else {
|
||||
browseDefinition$ = observableOf(route.data.browseDefinition);
|
||||
}
|
||||
@@ -37,19 +43,24 @@ export class BrowseByGuard implements CanActivate {
|
||||
const value = route.queryParams.value;
|
||||
const metadataTranslated = this.translate.instant(`browse.metadata.${id}`);
|
||||
return browseDefinition$.pipe(
|
||||
switchMap((browseDefinition: BrowseDefinition) => {
|
||||
if (hasValue(scope)) {
|
||||
const dso$: Observable<DSpaceObject> = this.dsoService.findById(scope).pipe(getFirstSucceededRemoteDataPayload());
|
||||
return dso$.pipe(
|
||||
map((dso: DSpaceObject) => {
|
||||
const name = this.dsoNameService.getName(dso);
|
||||
route.data = this.createData(title, id, browseDefinition, name, metadataTranslated, value, route);
|
||||
return true;
|
||||
})
|
||||
);
|
||||
switchMap((browseDefinition: BrowseDefinition | undefined) => {
|
||||
if (hasValue(browseDefinition)) {
|
||||
if (hasValue(scope)) {
|
||||
const dso$: Observable<DSpaceObject> = this.dsoService.findById(scope).pipe(getFirstSucceededRemoteDataPayload());
|
||||
return dso$.pipe(
|
||||
map((dso: DSpaceObject) => {
|
||||
const name = this.dsoNameService.getName(dso);
|
||||
route.data = this.createData(title, id, browseDefinition, name, metadataTranslated, value, route);
|
||||
return true;
|
||||
})
|
||||
);
|
||||
} else {
|
||||
route.data = this.createData(title, id, browseDefinition, '', metadataTranslated, value, route);
|
||||
return observableOf(true);
|
||||
}
|
||||
} else {
|
||||
route.data = this.createData(title, id, browseDefinition, '', metadataTranslated, value, route);
|
||||
return observableOf(true);
|
||||
void this.router.navigate([PAGE_NOT_FOUND_PATH]);
|
||||
return observableOf(false);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
@@ -37,6 +37,7 @@
|
||||
<a [routerLink]="node.route" class="lead">
|
||||
{{ dsoNameService.getName(node.payload) }}
|
||||
</a>
|
||||
<span *ngIf="node.payload.archivedItemsCount >= 0" class="archived-items-lead">[{{node.payload.archivedItemsCount}}]</span>
|
||||
</h5>
|
||||
</div>
|
||||
<ds-truncatable [id]="node.id">
|
||||
|
181
src/app/core/data/primary-bitstream.service.spec.ts
Normal file
181
src/app/core/data/primary-bitstream.service.spec.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { RequestService } from './request.service';
|
||||
import { Bitstream } from '../shared/bitstream.model';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { getMockRequestService } from '../../shared/mocks/request.service.mock';
|
||||
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { getMockRemoteDataBuildService } from '../../shared/mocks/remote-data-build.service.mock';
|
||||
import { PrimaryBitstreamService } from './primary-bitstream.service';
|
||||
import { BundleDataService } from './bundle-data.service';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
|
||||
import { CreateRequest, DeleteRequest, PostRequest, PutRequest } from './request.models';
|
||||
import { createFailedRemoteDataObject, createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||
import { Bundle } from '../shared/bundle.model';
|
||||
import { getTestScheduler } from 'jasmine-marbles';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
|
||||
describe('PrimaryBitstreamService', () => {
|
||||
let service: PrimaryBitstreamService;
|
||||
let objectCache: ObjectCacheService;
|
||||
let requestService: RequestService;
|
||||
let halService: HALEndpointService;
|
||||
let rdbService: RemoteDataBuildService;
|
||||
let notificationService: NotificationsService;
|
||||
let bundleDataService: BundleDataService;
|
||||
|
||||
const bitstream = Object.assign(new Bitstream(), {
|
||||
uuid: 'fake-bitstream',
|
||||
_links: {
|
||||
self: { href: 'fake-bitstream-self' }
|
||||
}
|
||||
});
|
||||
|
||||
const bundle = Object.assign(new Bundle(), {
|
||||
uuid: 'fake-bundle',
|
||||
_links: {
|
||||
self: { href: 'fake-bundle-self' },
|
||||
primaryBitstream: { href: 'fake-primary-bitstream-self' },
|
||||
}
|
||||
});
|
||||
|
||||
const url = 'fake-bitstream-url';
|
||||
|
||||
beforeEach(() => {
|
||||
objectCache = jasmine.createSpyObj('objectCache', {
|
||||
remove: jasmine.createSpy('remove')
|
||||
});
|
||||
requestService = getMockRequestService();
|
||||
halService = Object.assign(new HALEndpointServiceStub(url));
|
||||
|
||||
rdbService = getMockRemoteDataBuildService();
|
||||
notificationService = new NotificationsServiceStub() as any;
|
||||
bundleDataService = jasmine.createSpyObj('bundleDataService', {'findByHref': createSuccessfulRemoteDataObject$(bundle)});
|
||||
service = new PrimaryBitstreamService(requestService, rdbService, objectCache, halService, notificationService, bundleDataService);
|
||||
});
|
||||
|
||||
describe('getHttpOptions', () => {
|
||||
it('should return a HttpOptions object with text/url-list Context-Type header', () => {
|
||||
const result = (service as any).getHttpOptions();
|
||||
expect(result.headers.get('Content-Type')).toEqual('text/uri-list');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createAndSendRequest', () => {
|
||||
const testId = '12345-12345';
|
||||
const options = {};
|
||||
const testResult = createSuccessfulRemoteDataObject(new Bundle());
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(service as any, 'getHttpOptions').and.returnValue(options);
|
||||
(requestService.generateRequestId as jasmine.Spy<any>).and.returnValue(testId);
|
||||
spyOn(rdbService, 'buildFromRequestUUID').and.returnValue(observableOf(testResult));
|
||||
});
|
||||
|
||||
it('should return a Request object with the given constructor and the given parameters', () => {
|
||||
const result = (service as any).createAndSendRequest(CreateRequest, url, bitstream.self);
|
||||
const request = new CreateRequest(testId, url, bitstream.self, options);
|
||||
getTestScheduler().expectObservable(result).toBe('(a|)', { a: testResult });
|
||||
|
||||
expect(requestService.send).toHaveBeenCalledWith(request);
|
||||
expect(rdbService.buildFromRequestUUID).toHaveBeenCalledWith(testId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
const testResult = createSuccessfulRemoteDataObject(new Bundle());
|
||||
beforeEach(() => {
|
||||
spyOn((service as any), 'createAndSendRequest').and.returnValue(observableOf(testResult));
|
||||
});
|
||||
|
||||
it('should delegate the call to createAndSendRequest', () => {
|
||||
const result = service.create(bitstream, bundle);
|
||||
getTestScheduler().expectObservable(result).toBe('(a|)', { a: testResult });
|
||||
|
||||
expect((service as any).createAndSendRequest).toHaveBeenCalledWith(
|
||||
PostRequest,
|
||||
bundle._links.primaryBitstream.href,
|
||||
bitstream.self
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('put', () => {
|
||||
const testResult = createSuccessfulRemoteDataObject(new Bundle());
|
||||
beforeEach(() => {
|
||||
spyOn((service as any), 'createAndSendRequest').and.returnValue(observableOf(testResult));
|
||||
});
|
||||
|
||||
it('should delegate the call to createAndSendRequest and return the requested bundle', () => {
|
||||
const result = service.put(bitstream, bundle);
|
||||
getTestScheduler().expectObservable(result).toBe('(a|)', { a: testResult });
|
||||
|
||||
expect((service as any).createAndSendRequest).toHaveBeenCalledWith(
|
||||
PutRequest,
|
||||
bundle._links.primaryBitstream.href,
|
||||
bitstream.self
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('delete', () => {
|
||||
const testBundle = Object.assign(new Bundle(), {
|
||||
_links: {
|
||||
self: {
|
||||
href: 'test-href'
|
||||
},
|
||||
primaryBitstream: {
|
||||
href: 'test-primaryBitstream-href'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
describe('when the delete request succeeds', () => {
|
||||
const testResult = createSuccessfulRemoteDataObject(new Bundle());
|
||||
const bundleServiceResult = createSuccessfulRemoteDataObject(testBundle);
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn((service as any), 'createAndSendRequest').and.returnValue(observableOf(testResult));
|
||||
(bundleDataService.findByHref as jasmine.Spy<any>).and.returnValue(observableOf(bundleServiceResult));
|
||||
});
|
||||
|
||||
it('should delegate the call to createAndSendRequest', () => {
|
||||
const result = service.delete(testBundle);
|
||||
getTestScheduler().expectObservable(result).toBe('(a|)', { a: bundleServiceResult });
|
||||
|
||||
result.subscribe();
|
||||
|
||||
expect(bundleDataService.findByHref).toHaveBeenCalledWith(testBundle.self, false);
|
||||
|
||||
expect((service as any).createAndSendRequest).toHaveBeenCalledWith(
|
||||
DeleteRequest,
|
||||
testBundle._links.primaryBitstream.href,
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('when the delete request fails', () => {
|
||||
const testResult = createFailedRemoteDataObject();
|
||||
const bundleServiceResult = createSuccessfulRemoteDataObject(testBundle);
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn((service as any), 'createAndSendRequest').and.returnValue(observableOf(testResult));
|
||||
(bundleDataService.findByHref as jasmine.Spy<any>).and.returnValue(observableOf(bundleServiceResult));
|
||||
});
|
||||
|
||||
it('should delegate the call to createAndSendRequest and request the bundle from the bundleDataService', () => {
|
||||
const result = service.delete(testBundle);
|
||||
result.subscribe();
|
||||
expect((service as any).createAndSendRequest).toHaveBeenCalledWith(
|
||||
DeleteRequest,
|
||||
testBundle._links.primaryBitstream.href,
|
||||
);
|
||||
expect(bundleDataService.findByHref).toHaveBeenCalledWith(testBundle.self, true);
|
||||
|
||||
});
|
||||
|
||||
it('should delegate the call to createAndSendRequest and', () => {
|
||||
const result = service.delete(bundle);
|
||||
getTestScheduler().expectObservable(result).toBe('(a|)', { a: bundleServiceResult });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
119
src/app/core/data/primary-bitstream.service.ts
Normal file
119
src/app/core/data/primary-bitstream.service.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { Bitstream } from '../shared/bitstream.model';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { RequestService } from './request.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { Observable, switchMap } from 'rxjs';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { Bundle } from '../shared/bundle.model';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { HttpOptions } from '../dspace-rest/dspace-rest.service';
|
||||
import { HttpHeaders } from '@angular/common/http';
|
||||
import { GenericConstructor } from '../shared/generic-constructor';
|
||||
import { PutRequest, PostRequest, DeleteRequest } from './request.models';
|
||||
import { getAllCompletedRemoteData } from '../shared/operators';
|
||||
import { NoContent } from '../shared/NoContent.model';
|
||||
import { BundleDataService } from './bundle-data.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class PrimaryBitstreamService {
|
||||
|
||||
constructor(
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected halService: HALEndpointService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected bundleDataService: BundleDataService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of HttpOptions object needed from primary bitstream requests.
|
||||
* i.e. with a Content-Type header set to `text/uri-list`
|
||||
* @protected
|
||||
*/
|
||||
protected getHttpOptions(): HttpOptions {
|
||||
const options: HttpOptions = Object.create({});
|
||||
let headers = new HttpHeaders();
|
||||
headers = headers.append('Content-Type', 'text/uri-list');
|
||||
options.headers = headers;
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a request of the given type to the endpointURL with an optional primaryBitstreamSelfLink
|
||||
* as payload, and return the resulting Observable<RemoteData>
|
||||
*
|
||||
* @param requestType The type of request: PostRequest, PutRequest, or DeleteRequest
|
||||
* @param endpointURL The endpoint URL
|
||||
* @param primaryBitstreamSelfLink
|
||||
* @protected
|
||||
*/
|
||||
protected createAndSendRequest(
|
||||
requestType: GenericConstructor<PostRequest | PutRequest | DeleteRequest>,
|
||||
endpointURL: string,
|
||||
primaryBitstreamSelfLink?: string,
|
||||
): Observable<RemoteData<Bundle | NoContent>> {
|
||||
const requestId = this.requestService.generateRequestId();
|
||||
const request = new requestType(
|
||||
requestId,
|
||||
endpointURL,
|
||||
primaryBitstreamSelfLink,
|
||||
this.getHttpOptions()
|
||||
);
|
||||
|
||||
this.requestService.send(request);
|
||||
|
||||
return this.rdbService.buildFromRequestUUID(requestId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new primaryBitstream
|
||||
*
|
||||
* @param primaryBitstream The object to create
|
||||
* @param bundle The bundle to create it on
|
||||
*/
|
||||
create(primaryBitstream: Bitstream, bundle: Bundle): Observable<RemoteData<Bundle>> {
|
||||
return this.createAndSendRequest(
|
||||
PostRequest,
|
||||
bundle._links.primaryBitstream.href,
|
||||
primaryBitstream.self
|
||||
) as Observable<RemoteData<Bundle>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing primaryBitstream
|
||||
*
|
||||
* @param primaryBitstream The object to update
|
||||
* @param bundle The bundle to update it on
|
||||
*/
|
||||
put(primaryBitstream: Bitstream, bundle: Bundle): Observable<RemoteData<Bundle>> {
|
||||
return this.createAndSendRequest(
|
||||
PutRequest,
|
||||
bundle._links.primaryBitstream.href,
|
||||
primaryBitstream.self
|
||||
) as Observable<RemoteData<Bundle>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an existing primaryBitstream
|
||||
*
|
||||
* @param bundle The bundle to delete it from
|
||||
*/
|
||||
delete(bundle: Bundle): Observable<RemoteData<Bundle>> {
|
||||
return this.createAndSendRequest(
|
||||
DeleteRequest,
|
||||
bundle._links.primaryBitstream.href
|
||||
).pipe(
|
||||
getAllCompletedRemoteData(),
|
||||
switchMap((rd: RemoteData<NoContent>) => {
|
||||
return this.bundleDataService.findByHref(bundle.self, rd.hasFailed);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@@ -1,14 +1,16 @@
|
||||
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { SignpostingDataService } from './signposting-data.service';
|
||||
import { DspaceRestService } from '../dspace-rest/dspace-rest.service';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { of } from 'rxjs';
|
||||
import { SignpostingLink } from './signposting-links.model';
|
||||
import { APP_CONFIG } from '../../../config/app-config.interface';
|
||||
|
||||
describe('SignpostingDataService', () => {
|
||||
let service: SignpostingDataService;
|
||||
let restServiceSpy: jasmine.SpyObj<DspaceRestService>;
|
||||
let halServiceSpy: jasmine.SpyObj<HALEndpointService>;
|
||||
|
||||
const mocklink = {
|
||||
href: 'http://test.org',
|
||||
rel: 'test',
|
||||
@@ -30,21 +32,25 @@ describe('SignpostingDataService', () => {
|
||||
statusCode: 500
|
||||
};
|
||||
|
||||
const environmentRest = {
|
||||
rest: {
|
||||
baseUrl: 'http://localhost:8080'
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
const restSpy = jasmine.createSpyObj('DspaceRestService', ['get', 'getWithHeaders']);
|
||||
const halSpy = jasmine.createSpyObj('HALEndpointService', ['getRootHref']);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
SignpostingDataService,
|
||||
{ provide: DspaceRestService, useValue: restSpy },
|
||||
{ provide: HALEndpointService, useValue: halSpy }
|
||||
{ provide: APP_CONFIG, useValue: environmentRest },
|
||||
{ provide: DspaceRestService, useValue: restSpy }
|
||||
]
|
||||
});
|
||||
|
||||
service = TestBed.inject(SignpostingDataService);
|
||||
restServiceSpy = TestBed.inject(DspaceRestService) as jasmine.SpyObj<DspaceRestService>;
|
||||
halServiceSpy = TestBed.inject(HALEndpointService) as jasmine.SpyObj<HALEndpointService>;
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
@@ -55,8 +61,6 @@ describe('SignpostingDataService', () => {
|
||||
const uuid = '123';
|
||||
const baseUrl = 'http://localhost:8080';
|
||||
|
||||
halServiceSpy.getRootHref.and.returnValue(`${baseUrl}/api`);
|
||||
|
||||
restServiceSpy.get.and.returnValue(of(mockResponse));
|
||||
|
||||
let result: SignpostingLink[];
|
||||
@@ -70,7 +74,6 @@ describe('SignpostingDataService', () => {
|
||||
tick();
|
||||
|
||||
expect(result).toEqual(expectedResult);
|
||||
expect(halServiceSpy.getRootHref).toHaveBeenCalled();
|
||||
expect(restServiceSpy.get).toHaveBeenCalledWith(`${baseUrl}/signposting/links/${uuid}`);
|
||||
}));
|
||||
|
||||
@@ -78,8 +81,6 @@ describe('SignpostingDataService', () => {
|
||||
const uuid = '123';
|
||||
const baseUrl = 'http://localhost:8080';
|
||||
|
||||
halServiceSpy.getRootHref.and.returnValue(`${baseUrl}/api`);
|
||||
|
||||
restServiceSpy.get.and.returnValue(of(mockErrResponse));
|
||||
|
||||
let result: any;
|
||||
@@ -91,7 +92,6 @@ describe('SignpostingDataService', () => {
|
||||
tick();
|
||||
|
||||
expect(result).toEqual([]);
|
||||
expect(halServiceSpy.getRootHref).toHaveBeenCalled();
|
||||
expect(restServiceSpy.get).toHaveBeenCalledWith(`${baseUrl}/signposting/links/${uuid}`);
|
||||
}));
|
||||
});
|
||||
|
@@ -1,12 +1,12 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
|
||||
import { DspaceRestService } from '../dspace-rest/dspace-rest.service';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { RawRestResponse } from '../dspace-rest/raw-rest-response.model';
|
||||
import { SignpostingLink } from './signposting-links.model';
|
||||
import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface';
|
||||
|
||||
/**
|
||||
* Service responsible for handling requests related to the Signposting endpoint
|
||||
@@ -16,7 +16,7 @@ import { SignpostingLink } from './signposting-links.model';
|
||||
})
|
||||
export class SignpostingDataService {
|
||||
|
||||
constructor(private restService: DspaceRestService, protected halService: HALEndpointService) {
|
||||
constructor(@Inject(APP_CONFIG) protected appConfig: AppConfig, private restService: DspaceRestService) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -25,7 +25,7 @@ export class SignpostingDataService {
|
||||
* @param uuid
|
||||
*/
|
||||
getLinks(uuid: string): Observable<SignpostingLink[]> {
|
||||
const baseUrl = this.halService.getRootHref().replace('/api', '');
|
||||
const baseUrl = `${this.appConfig.rest.baseUrl}`;
|
||||
|
||||
return this.restService.get(`${baseUrl}/signposting/links/${uuid}`).pipe(
|
||||
catchError((err) => {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { deserialize, inheritSerialization } from 'cerialize';
|
||||
import {autoserialize, deserialize, inheritSerialization} from 'cerialize';
|
||||
import { Observable } from 'rxjs';
|
||||
import { link, typedObject } from '../cache/builders/build-decorators';
|
||||
import { PaginatedList } from '../data/paginated-list.model';
|
||||
@@ -16,12 +16,17 @@ import { COMMUNITY } from './community.resource-type';
|
||||
import { Community } from './community.model';
|
||||
import { ChildHALResource } from './child-hal-resource.model';
|
||||
import { HandleObject } from './handle-object.model';
|
||||
import { excludeFromEquals } from '../utilities/equals.decorators';
|
||||
|
||||
@typedObject
|
||||
@inheritSerialization(DSpaceObject)
|
||||
export class Collection extends DSpaceObject implements ChildHALResource, HandleObject {
|
||||
static type = COLLECTION;
|
||||
|
||||
@excludeFromEquals
|
||||
@autoserialize
|
||||
archivedItemsCount: number;
|
||||
|
||||
/**
|
||||
* The {@link HALLink}s for this Collection
|
||||
*/
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { deserialize, inheritSerialization } from 'cerialize';
|
||||
import {autoserialize, deserialize, inheritSerialization} from 'cerialize';
|
||||
import { Observable } from 'rxjs';
|
||||
import { link, typedObject } from '../cache/builders/build-decorators';
|
||||
import { PaginatedList } from '../data/paginated-list.model';
|
||||
@@ -12,12 +12,17 @@ import { DSpaceObject } from './dspace-object.model';
|
||||
import { HALLink } from './hal-link.model';
|
||||
import { ChildHALResource } from './child-hal-resource.model';
|
||||
import { HandleObject } from './handle-object.model';
|
||||
import {excludeFromEquals} from '../utilities/equals.decorators';
|
||||
|
||||
@typedObject
|
||||
@inheritSerialization(DSpaceObject)
|
||||
export class Community extends DSpaceObject implements ChildHALResource, HandleObject {
|
||||
static type = COMMUNITY;
|
||||
|
||||
@excludeFromEquals
|
||||
@autoserialize
|
||||
archivedItemsCount: number;
|
||||
|
||||
/**
|
||||
* The {@link HALLink}s for this Community
|
||||
*/
|
||||
|
@@ -1,37 +1,95 @@
|
||||
<h2>{{ 'info.end-user-agreement.head' | translate }}</h2>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Nunc sed velit dignissim sodales ut eu. In ante metus dictum at tempor. Diam phasellus vestibulum lorem sed risus. Sed cras ornare arcu dui vivamus. Sit amet consectetur adipiscing elit pellentesque. Id velit ut tortor pretium viverra suspendisse potenti. Sed euismod nisi porta lorem mollis aliquam ut. Justo laoreet sit amet cursus sit amet dictum sit. Ullamcorper morbi tincidunt ornare massa eget egestas.
|
||||
</p>
|
||||
<p>
|
||||
In iaculis nunc sed augue lacus. Curabitur vitae nunc sed velit dignissim sodales ut eu sem. Tellus id interdum velit laoreet id donec ultrices tincidunt arcu. Quis vel eros donec ac odio tempor. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae. Varius quam quisque id diam vel quam. Nisl tincidunt eget nullam non nisi est sit. Nunc aliquet bibendum enim facilisis. Aenean sed adipiscing diam donec adipiscing. Convallis tellus id interdum velit laoreet. Massa placerat duis ultricies lacus sed turpis tincidunt. Sed cras ornare arcu dui vivamus arcu. Egestas integer eget aliquet nibh praesent tristique. Sit amet purus gravida quis blandit turpis cursus in hac. Porta non pulvinar neque laoreet suspendisse. Quis risus sed vulputate odio ut. Dignissim enim sit amet venenatis urna cursus.
|
||||
</p>
|
||||
<p>
|
||||
Interdum velit laoreet id donec ultrices tincidunt arcu non sodales. Massa sapien faucibus et molestie. Dictumst vestibulum rhoncus est pellentesque elit ullamcorper. Metus dictum at tempor commodo ullamcorper. Tincidunt lobortis feugiat vivamus at augue eget. Non diam phasellus vestibulum lorem sed risus ultricies. Neque aliquam vestibulum morbi blandit cursus risus at ultrices mi. Euismod lacinia at quis risus sed. Lorem mollis aliquam ut porttitor leo a diam. Ipsum dolor sit amet consectetur. Ante in nibh mauris cursus mattis molestie a iaculis at. Commodo ullamcorper a lacus vestibulum. Pellentesque elit eget gravida cum sociis. Sit amet commodo nulla facilisi nullam vehicula. Vehicula ipsum a arcu cursus vitae congue mauris rhoncus aenean.
|
||||
</p>
|
||||
<p>
|
||||
Ac turpis egestas maecenas pharetra convallis. Lacus sed viverra tellus in. Nullam eget felis eget nunc lobortis mattis aliquam faucibus purus. Id aliquet risus feugiat in ante metus dictum at. Quis enim lobortis scelerisque fermentum dui faucibus. Eu volutpat odio facilisis mauris sit amet massa vitae tortor. Tellus elementum sagittis vitae et leo. Cras sed felis eget velit aliquet sagittis. Proin fermentum leo vel orci porta non pulvinar neque laoreet. Dui sapien eget mi proin sed libero enim. Ultrices mi tempus imperdiet nulla malesuada. Mattis molestie a iaculis at. Turpis massa sed elementum tempus egestas.
|
||||
</p>
|
||||
<p>
|
||||
Dui faucibus in ornare quam viverra orci sagittis eu volutpat. Cras adipiscing enim eu turpis. Ac felis donec et odio pellentesque. Iaculis nunc sed augue lacus viverra vitae congue eu consequat. Posuere lorem ipsum dolor sit amet consectetur adipiscing elit duis. Elit eget gravida cum sociis natoque penatibus. Id faucibus nisl tincidunt eget nullam non. Sagittis aliquam malesuada bibendum arcu vitae. Fermentum leo vel orci porta. Aliquam ultrices sagittis orci a scelerisque purus semper. Diam maecenas sed enim ut sem viverra aliquet eget sit. Et ultrices neque ornare aenean euismod. Eu mi bibendum neque egestas congue quisque egestas diam. Eget lorem dolor sed viverra. Ut lectus arcu bibendum at. Rutrum tellus pellentesque eu tincidunt tortor. Vitae congue eu consequat ac. Elit ullamcorper dignissim cras tincidunt. Sit amet volutpat consequat mauris nunc congue nisi.
|
||||
</p>
|
||||
<p>
|
||||
Cursus in hac habitasse platea dictumst quisque sagittis purus. Placerat duis ultricies lacus sed turpis tincidunt. In egestas erat imperdiet sed euismod nisi porta lorem mollis. Non nisi est sit amet facilisis magna. In massa tempor nec feugiat nisl pretium fusce. Pulvinar neque laoreet suspendisse interdum consectetur. Ullamcorper morbi tincidunt ornare massa eget egestas purus viverra accumsan. Fringilla urna porttitor rhoncus dolor purus non enim. Mauris nunc congue nisi vitae suscipit. Commodo elit at imperdiet dui accumsan sit amet nulla. Tempor id eu nisl nunc mi ipsum faucibus. Porta non pulvinar neque laoreet suspendisse. Nec nam aliquam sem et tortor consequat.
|
||||
</p>
|
||||
<p>
|
||||
Eget nunc lobortis mattis aliquam faucibus purus. Odio tempor orci dapibus ultrices. Sed nisi lacus sed viverra tellus. Elit ullamcorper dignissim cras tincidunt. Porttitor rhoncus dolor purus non enim praesent elementum facilisis. Viverra orci sagittis eu volutpat odio. Pharetra massa massa ultricies mi quis. Lectus vestibulum mattis ullamcorper velit sed ullamcorper. Pulvinar neque laoreet suspendisse interdum consectetur. Vitae auctor eu augue ut. Arcu dictum varius duis at consectetur lorem donec. Massa sed elementum tempus egestas sed sed. Risus viverra adipiscing at in tellus integer. Vulputate enim nulla aliquet porttitor lacus luctus accumsan. Pharetra massa massa ultricies mi. Elementum eu facilisis sed odio morbi quis commodo odio. Tincidunt lobortis feugiat vivamus at. Felis donec et odio pellentesque diam volutpat commodo sed. Risus feugiat in ante metus dictum at tempor commodo ullamcorper. Fringilla phasellus faucibus scelerisque eleifend donec pretium vulputate.
|
||||
</p>
|
||||
<p>
|
||||
Lectus proin nibh nisl condimentum id venenatis a condimentum. Id consectetur purus ut faucibus pulvinar elementum integer enim. Non pulvinar neque laoreet suspendisse interdum consectetur. Est pellentesque elit ullamcorper dignissim cras tincidunt lobortis feugiat vivamus. Suscipit tellus mauris a diam maecenas sed enim ut sem. Dolor purus non enim praesent elementum facilisis. Non enim praesent elementum facilisis leo vel. Ultricies leo integer malesuada nunc vel risus commodo viverra maecenas. Nulla porttitor massa id neque aliquam vestibulum. Erat velit scelerisque in dictum non consectetur. Amet cursus sit amet dictum. Nec tincidunt praesent semper feugiat nibh. Rutrum quisque non tellus orci ac auctor. Sagittis aliquam malesuada bibendum arcu vitae elementum. Massa tincidunt dui ut ornare lectus sit amet est. Aliquet porttitor lacus luctus accumsan tortor posuere ac. Quis hendrerit dolor magna eget est lorem ipsum dolor sit. Lectus mauris ultrices eros in.
|
||||
</p>
|
||||
<p>
|
||||
Massa massa ultricies mi quis hendrerit dolor magna. Est ullamcorper eget nulla facilisi etiam dignissim diam. Vulputate sapien nec sagittis aliquam malesuada. Nisi porta lorem mollis aliquam ut porttitor leo a diam. Tempus quam pellentesque nec nam. Faucibus vitae aliquet nec ullamcorper sit amet risus nullam eget. Gravida arcu ac tortor dignissim convallis aenean et tortor. A scelerisque purus semper eget duis at tellus at. Viverra ipsum nunc aliquet bibendum enim. Semper feugiat nibh sed pulvinar proin gravida hendrerit. Et ultrices neque ornare aenean euismod. Consequat semper viverra nam libero justo laoreet. Nunc mattis enim ut tellus elementum sagittis. Consectetur lorem donec massa sapien faucibus et. Vel risus commodo viverra maecenas accumsan lacus vel facilisis. Diam sollicitudin tempor id eu nisl nunc. Dolor magna eget est lorem ipsum dolor. Adipiscing elit pellentesque habitant morbi tristique.
|
||||
</p>
|
||||
<p>
|
||||
Nec sagittis aliquam malesuada bibendum arcu vitae elementum curabitur. Egestas fringilla phasellus faucibus scelerisque eleifend donec pretium vulputate sapien. Porttitor leo a diam sollicitudin tempor. Pellentesque dignissim enim sit amet venenatis urna cursus eget nunc. Posuere sollicitudin aliquam ultrices sagittis orci a scelerisque. Vehicula ipsum a arcu cursus vitae congue mauris rhoncus. Leo urna molestie at elementum. Duis tristique sollicitudin nibh sit amet commodo nulla facilisi. Libero id faucibus nisl tincidunt eget nullam. Tellus elementum sagittis vitae et leo duis ut diam. Sodales ut etiam sit amet nisl purus in mollis. Ipsum faucibus vitae aliquet nec ullamcorper sit amet risus. Lacus laoreet non curabitur gravida arcu ac tortor dignissim convallis. Aliquam malesuada bibendum arcu vitae elementum. Leo vel orci porta non pulvinar neque laoreet. Ipsum suspendisse ultrices gravida dictum fusce.
|
||||
</p>
|
||||
<p>
|
||||
Egestas erat imperdiet sed euismod nisi porta lorem. Venenatis a condimentum vitae sapien pellentesque habitant. Sit amet luctus venenatis lectus magna fringilla urna porttitor. Orci sagittis eu volutpat odio facilisis mauris sit amet massa. Ut enim blandit volutpat maecenas volutpat blandit aliquam. Libero volutpat sed cras ornare. Molestie ac feugiat sed lectus vestibulum mattis ullamcorper velit sed. Diam quis enim lobortis scelerisque fermentum dui. Pellentesque habitant morbi tristique senectus et netus. Auctor urna nunc id cursus metus aliquam eleifend. Elit scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique. Sed risus ultricies tristique nulla aliquet enim tortor. Tincidunt arcu non sodales neque sodales ut. Sed lectus vestibulum mattis ullamcorper velit sed ullamcorper morbi tincidunt.
|
||||
</p>
|
||||
<p>
|
||||
Pulvinar etiam non quam lacus suspendisse faucibus. Eu mi bibendum neque egestas congue. Egestas purus viverra accumsan in nisl nisi scelerisque eu. Vulputate enim nulla aliquet porttitor lacus luctus accumsan. Eu non diam phasellus vestibulum. Semper feugiat nibh sed pulvinar. Ante in nibh mauris cursus mattis molestie a. Maecenas accumsan lacus vel facilisis volutpat. Non quam lacus suspendisse faucibus. Quis commodo odio aenean sed adipiscing. Vel elit scelerisque mauris pellentesque pulvinar pellentesque habitant morbi. Sed cras ornare arcu dui vivamus arcu felis. Tortor vitae purus faucibus ornare suspendisse sed. Morbi tincidunt ornare massa eget egestas purus viverra. Nibh cras pulvinar mattis nunc. Luctus venenatis lectus magna fringilla urna porttitor. Enim blandit volutpat maecenas volutpat blandit aliquam etiam erat. Malesuada pellentesque elit eget gravida cum sociis natoque penatibus et. Felis eget nunc lobortis mattis aliquam faucibus purus in. Vivamus arcu felis bibendum ut.
|
||||
</p>
|
||||
<h1>{{ 'info.end-user-agreement.head' | translate }}</h1>
|
||||
<p><em>Last updated May 4, 2023</em></p>
|
||||
|
||||
<h2>Agreement to terms</h2>
|
||||
<p>These Terms of Use constitute a legally binding agreement made between you, whether personally or on behalf of an entity ("you") and {{ 'repository.title' | translate }} ("Company", "we", "us", or "our"), concerning your access to and use of this website as well as any other media form, media channel, mobile website or mobile application related, linked, or otherwise connected thereto (collectively, the "Site"). You agree that by accessing the Site, you have read, understood, and agreed to be bound by all of these Terms of Use and any future amendments thereof.</p>
|
||||
<p>Supplemental terms and conditions or documents that may be posted on the Site from time to time are hereby expressly incorporated herein by reference. We reserve the right, in our sole discretion, to make changes or modifications to these Terms of Use at any time and for any reason. We will alert you about any changes by updating the "Last updated" date of these Terms of Use, and you waive any right to receive specific notice of each such change. Please ensure that you check the applicable Terms every time you use our Site so that you understand which Terms apply. You will be subject to, and will be deemed to have been made aware of and to have accepted, the changes in any revised Terms of Use by your continued use of the Site after the date such revised Terms of Use are posted.</p>
|
||||
<p>The information provided on the Site is not intended for distribution to or use by any person or entity in any jurisdiction or country where such distribution or use would be contrary to law or regulation or which would subject us to any registration requirement within such jurisdiction or country. Accordingly, those persons who choose to access the Site from other locations do so on their own initiative and are solely responsible for compliance with local laws, if and to the extent local laws are applicable.</p>
|
||||
|
||||
<h2>Intellectual property rights</h2>
|
||||
<p>Unless otherwise indicated, the Site is our proprietary property and all source code, databases, functionality, software, website designs, audio, video, text, photographs, and graphics on the Site (collectively, the "Content") and the trademarks, service marks, and logos contained therein (the "Marks") are owned or controlled by us or licensed to us, and are protected by copyright and trademark laws and various other intellectual property rights and unfair competition laws of {{ 'info.end-user-agreement.hosting-country' | translate }}, international copyright laws, and international conventions. The Content and the Marks are provided on the Site "AS IS" for your information and personal use only. Except as expressly provided in these Terms of Use, no part of the Site and no Content<sup><a [routerLink]="'.'" fragment="comment-a" id="ref-a">[a]</a></sup> or Marks may be copied, reproduced, aggregated, republished, uploaded, posted, publicly displayed, encoded, translated, transmitted, distributed, sold, licensed, or otherwise exploited for any commercial purpose whatsoever, without our express prior written permission.</p>
|
||||
<p>Provided that you are eligible to use the Site, you are granted a limited license to access and use the Site and to download or print a copy of any portion of the Content to which you have properly gained access solely for your personal, non-commercial use. We reserve all rights not expressly granted to you in and to the Site, the Content and the Marks.</p>
|
||||
|
||||
<h2>User representations</h2>
|
||||
<p>By using the Site, you represent and warrant that: (1) all registration information you submit will be true, accurate, current, and complete; (2) you will maintain the accuracy of such information and promptly update such registration information as necessary; (3) you have the legal capacity and you agree to comply with these Terms of Use; (4) you will not use the Site for any illegal or unauthorized purpose; and (5) your use of the Site will not violate any applicable law or regulation.</p>
|
||||
<p>If you provide any information that is untrue, inaccurate, not current, or incomplete, we have the right to suspend or terminate your account and refuse any and all current or future use of the Site (or any portion thereof).</p>
|
||||
|
||||
<h2>User registration</h2>
|
||||
<p>You may be required to register with the Site. You agree to keep your password confidential and will be responsible for all use of your account and password. We reserve the right to remove, reclaim, or change a username you select if we determine, in our sole discretion, that such username is inappropriate, obscene, or otherwise objectionable.</p>
|
||||
|
||||
<h2>Prohibited activities</h2>
|
||||
<p>You may not access or use the Site for any purpose other than that for which we make the Site available. The Site may not be used in connection with any commercial endeavors except those that are specifically endorsed or approved by us.</p>
|
||||
<p>As a user of the Site, you agree not to:</p>
|
||||
<ul>
|
||||
<li>Systematically retrieve data or other content from the Site to create or compile, directly or indirectly, a collection, compilation, database, or directory without written permission from us.</li>
|
||||
<li>Trick, defraud, or mislead us and other users, especially in any attempt to learn sensitive account information such as user passwords.</li>
|
||||
<li>Circumvent, disable, or otherwise interfere with security-related features of the Site, including features that prevent or restrict the use or copying of any Content or enforce limitations on the use of the Site and/or the Content contained therein.</li>
|
||||
<li>Disparage, tarnish, or otherwise harm, in our opinion, us and/or the Site.</li>
|
||||
<li>Use any information obtained from the Site in order to harass, abuse, or harm another person.</li>
|
||||
<li>Make improper use of our support services or submit false reports of abuse or misconduct.</li>
|
||||
<li>Use the Site in a manner inconsistent with any applicable laws or regulations.</li>
|
||||
<li>Engage in unauthorized framing of or linking to the Site.</li>
|
||||
<li>Upload or transmit (or attempt to upload or to transmit) viruses, Trojan horses, or other material, including excessive use of capital letters and spamming (continuous posting of repetitive text), that interferes with any party's uninterrupted use and enjoyment of the Site or modifies, impairs, disrupts, alters, or interferes with the use, features, functions, operation, or maintenance of the Site.</li>
|
||||
<li>Delete the copyright or other proprietary rights notice from any Content.</li>
|
||||
<li>Attempt to impersonate another user or person or use the username and password of another user.</li>
|
||||
<li>Upload or transmit (or attempt to upload or to transmit) any material that acts as a passive or active information collection or transmission mechanism, including without limitation, clear graphics interchange formats ("gifs"), 1x1 pixels, web bugs, cookies, or other similar devices (sometimes referred to as "spyware" or "passive collection mechanisms" or "pcms").</li>
|
||||
<li>Interfere with, disrupt, or create an undue burden on the Site or the networks or services connected to the Site.</li>
|
||||
<li>Harass, annoy, intimidate, or threaten any of our employees or agents engaged in providing any portion of the Site to you.</li>
|
||||
<li>Attempt to bypass any measures of the Site designed to prevent or restrict access to the Site, or any portion of the Site.</li>
|
||||
<li>Make any unauthorized use of the Site, including collecting usernames and/or email addresses of users by electronic or other means for the purpose of sending unsolicited email or other forms of electronic communication, or creating user accounts by automated means or under false pretenses.</li>
|
||||
<li>Use the Site as part of any effort to compete with us or otherwise use the Site and/or the Content for any revenue-generating endeavor or commercial enterprise.</li>
|
||||
</ul>
|
||||
|
||||
<h2>User generated contributions</h2>
|
||||
<p>The Site may provide you with the opportunity to create, submit, post, display, transmit, perform, publish, distribute, or broadcast content and materials to us or on the Site, including but not limited to text, writings, video, audio, photographs, graphics, comments, suggestions, or personal information or other material (collectively, "Contributions"). Contributions may be viewable by other users of the Site and through third-party websites. As such, any Contributions you transmit may be treated as non-confidential and non-proprietary. When you create or make available any Contributions, you thereby represent and warrant that:</p>
|
||||
<ul>
|
||||
<li>The creation, distribution, transmission, public display, or performance, and the accessing, downloading, or copying of your Contributions do not and will not infringe the proprietary rights, including but not limited to the copyright, patent, trademark, trade secret, or moral rights of any third party.</li>
|
||||
<li>You are the creator and owner of or have the necessary licenses, rights, consents, releases, and permissions to use and to authorize us, the Site, and other users of the Site to use your Contributions in any manner contemplated by the Site and these Terms of Use.</li>
|
||||
<li>You have the written consent, release, and/or permission of each and every identifiable individual person in your Contributions to use the name or likeness of each and every such identifiable individual person to enable inclusion and use of your Contributions in any manner contemplated by the Site and these Terms of Use.</li>
|
||||
<li>Your Contributions are not false, inaccurate, or misleading.</li>
|
||||
<li>Your Contributions are not unsolicited or unauthorized advertising, promotional materials, pyramid schemes, chain letters, spam, mass mailings, or other forms of solicitation.</li>
|
||||
<li>Your Contributions are not obscene, lewd, lascivious, filthy, violent, harassing, libelous, slanderous, or otherwise objectionable (as determined by us).</li>
|
||||
<li>Your Contributions do not ridicule, mock, disparage, intimidate, or abuse anyone.</li>
|
||||
<li>Your Contributions are not used to harass or threaten (in the legal sense of those terms) any other person, do not create and are not used to promote violence against a specific person or class of people.</li>
|
||||
<li>Your Contributions do not violate any applicable law, regulation, or rule.</li>
|
||||
<li>Your Contributions do not violate the privacy or publicity rights of any third party.</li>
|
||||
<li>Your Contributions do not violate any applicable law concerning child pornography, or otherwise intended to protect the health or well-being of minors.</li>
|
||||
<li>Your Contributions do not include any offensive comments that are connected to race, national origin, gender, sexual preference, colour, religion, creed or physical handicap.</li>
|
||||
<li>Your Contributions do not otherwise violate, or link to material that violates, any provision of these Terms of Use, or any applicable law or regulation.</li>
|
||||
</ul>
|
||||
<p>Any use of the Site in violation of the foregoing violates these Terms of Use and may result in, among other things, termination or suspension of your rights to use the Site.</p>
|
||||
|
||||
<h2>Site management</h2>
|
||||
<p>We reserve the right, but not the obligation, to: (1) monitor the Site for violations of these Terms of Use; (2) take appropriate legal action against anyone who, in our sole discretion, violates the law or these Terms of Use, including without limitation, reporting such user to law enforcement authorities; (3) in our sole discretion and without limitation, refuse, restrict access to, limit the availability of, or disable (to the extent technologically feasible) any of your Contributions or any portion thereof; (4) in our sole discretion and without limitation, notice, or liability, to remove from the Site or otherwise disable all files and content that are excessive in size or are in any way burdensome to our systems; and (5) otherwise manage the Site in a manner designed to protect our rights and property and to facilitate the proper functioning of the Site.</p>
|
||||
|
||||
<h2>Privacy policy</h2>
|
||||
<p>We care about data privacy and security. Please review our <a [routerLink]="['/info/privacy']" target="_blank" rel="noopener noreferrer">Privacy Policy</a>. By using the Site, you agree to be bound by our Privacy Policy, which is incorporated into these Terms of Use.</p>
|
||||
<p>Please be advised the Site is hosted in {{ 'info.end-user-agreement.hosting-country' | translate }}. If you access the Site from any other region of the world with laws or other requirements governing personal data collection, use, or disclosure that differ from applicable laws in {{ 'info.end-user-agreement.hosting-country' | translate }}, then through your continued use of the Site, you are transferring your data to {{ 'info.end-user-agreement.hosting-country' | translate }}, and you agree to have your data transferred to and processed in {{ 'info.end-user-agreement.hosting-country' | translate }}.</p>
|
||||
|
||||
<h2>Term and termination</h2>
|
||||
<p>These Terms of Use shall remain in full force and effect while you use the Site. WITHOUT LIMITING ANY OTHER PROVISION OF THESE TERMS OF USE, WE RESERVE THE RIGHT TO, IN OUR SOLE DISCRETION AND WITHOUT NOTICE OR LIABILITY, DENY ACCESS TO AND USE OF THE SITE (INCLUDING BLOCKING CERTAIN IP ADDRESSES), TO ANY PERSON FOR ANY REASON OR FOR NO REASON, INCLUDING WITHOUT LIMITATION FOR BREACH OF ANY REPRESENTATION, WARRANTY, OR COVENANT CONTAINED IN THESE TERMS OF USE OR OF ANY APPLICABLE LAW OR REGULATION. WE MAY TERMINATE YOUR USE OR PARTICIPATION IN THE SITE OR DELETE YOUR ACCOUNT AND ANY CONTENT OR INFORMATION THAT YOU POSTED AT ANY TIME, WITHOUT WARNING, IN OUR SOLE DISCRETION.</p>
|
||||
<p>If we terminate or suspend your account for any reason, you are prohibited from registering and creating a new account under your name, a fake or borrowed name, or the name of any third party, even if you may be acting on behalf of the third party. In addition to terminating or suspending your account, we reserve the right to take appropriate legal action, including without limitation pursuing civil, criminal, and injunctive redress.</p>
|
||||
|
||||
<h2>Modifications and interruptions</h2>
|
||||
<p>We reserve the right to change, modify, or remove the contents of the Site at any time or for any reason at our sole discretion without notice. However, we have no obligation to update any information on our Site. We also reserve the right to modify or discontinue all or part of the Site without notice at any time. We will not be liable to you or any third party for any modification, change, suspension, or discontinuance of the Site.</p>
|
||||
<p>We cannot guarantee the Site will be available at all times. We may experience hardware, software, or other problems or need to perform maintenance related to the Site, resulting in interruptions, delays, or errors. We reserve the right to change, revise, update, suspend, discontinue, or otherwise modify the Site at any time or for any reason without notice to you. You agree that we have no liability whatsoever for any loss, damage, or inconvenience caused by your inability to access or use the Site during any downtime or discontinuance of the Site. Nothing in these Terms of Use will be construed to obligate us to maintain and support the Site or to supply any corrections, updates, or releases in connection therewith.</p>
|
||||
|
||||
<h2>Corrections</h2>
|
||||
<p>There may be information on the Site that contains typographical errors, inaccuracies, or omissions. We reserve the right to correct any errors, inaccuracies, or omissions and to change or update the information on the Site at any time, without prior notice.</p>
|
||||
|
||||
<h2>Disclaimer</h2>
|
||||
<p>THE SITE IS PROVIDED ON AN AS-IS AND AS-AVAILABLE BASIS. YOU AGREE THAT YOUR USE OF THE SITE AND OUR SERVICES WILL BE AT YOUR SOLE RISK. TO THE FULLEST EXTENT PERMITTED BY LAW, WE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, IN CONNECTION WITH THE SITE AND YOUR USE THEREOF, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. WE MAKE NO WARRANTIES OR REPRESENTATIONS ABOUT THE ACCURACY OR COMPLETENESS OF THE SITE'S CONTENT OR THE CONTENT OF ANY WEBSITES LINKED TO THE SITE AND WE WILL ASSUME NO LIABILITY OR RESPONSIBILITY FOR ANY (1) ERRORS, MISTAKES, OR INACCURACIES OF CONTENT AND MATERIALS, (2) PERSONAL INJURY OR PROPERTY DAMAGE, OF ANY NATURE WHATSOEVER, RESULTING FROM YOUR ACCESS TO AND USE OF THE SITE, (3) ANY UNAUTHORIZED ACCESS TO OR USE OF OUR SECURE SERVERS AND/OR ANY AND ALL PERSONAL INFORMATION AND/OR FINANCIAL INFORMATION STORED THEREIN, (4) ANY INTERRUPTION OR CESSATION OF TRANSMISSION TO OR FROM THE SITE, (5) ANY BUGS, VIRUSES, TROJAN HORSES, OR THE LIKE WHICH MAY BE TRANSMITTED TO OR THROUGH THE SITE BY ANY THIRD PARTY, AND/OR (6) ANY ERRORS OR OMISSIONS IN ANY CONTENT AND MATERIALS OR FOR ANY LOSS OR DAMAGE OF ANY KIND INCURRED AS A RESULT OF THE USE OF ANY CONTENT POSTED, TRANSMITTED, OR OTHERWISE MADE AVAILABLE VIA THE SITE. WE DO NOT WARRANT, ENDORSE, GUARANTEE, OR ASSUME RESPONSIBILITY FOR ANY PRODUCT OR SERVICE ADVERTISED OR OFFERED BY A THIRD PARTY THROUGH THE SITE, ANY HYPERLINKED WEBSITE, OR ANY WEBSITE OR MOBILE APPLICATION FEATURED IN ANY BANNER OR OTHER ADVERTISING, AND WE WILL NOT BE A PARTY TO OR IN ANY WAY BE RESPONSIBLE FOR MONITORING ANY TRANSACTION BETWEEN YOU AND ANY THIRD-PARTY PROVIDERS OF PRODUCTS OR SERVICES. AS WITH THE PURCHASE OF A PRODUCT OR SERVICE THROUGH ANY MEDIUM OR IN ANY ENVIRONMENT, YOU SHOULD USE YOUR BEST JUDGMENT AND EXERCISE CAUTION WHERE APPROPRIATE.</p>
|
||||
|
||||
<h2>Limitations of liability</h2>
|
||||
<p>IN NO EVENT WILL WE OR OUR DIRECTORS, EMPLOYEES, OR AGENTS BE LIABLE TO YOU OR ANY THIRD PARTY FOR ANY DIRECT, INDIRECT, CONSEQUENTIAL, EXEMPLARY, INCIDENTAL, SPECIAL, OR PUNITIVE DAMAGES, INCLUDING LOST PROFIT, LOST REVENUE, LOSS OF DATA, OR OTHER DAMAGES ARISING FROM YOUR USE OF THE SITE, EVEN IF WE HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.</p>
|
||||
|
||||
<h2>Indemnification</h2>
|
||||
<p>You agree to defend, indemnify, and hold us harmless, including our subsidiaries, affiliates, and all of our respective officers, agents, partners, and employees, from and against any loss, damage, liability, claim, or demand, including reasonable attorneys' fees and expenses, made by any third party due to or arising out of: (1) your Contributions; (2) use of the Site; (3) breach of these Terms of Use; (4) any breach of your representations and warranties set forth in these Terms of Use; (5) your violation of the rights of a third party, including but not limited to intellectual property rights; or (6) any overt harmful act toward any other user of the Site with whom you connected via the Site. Notwithstanding the foregoing, we reserve the right, at your expense, to assume the exclusive defense and control of any matter for which you are required to indemnify us, and you agree to cooperate, at your expense, with our defense of such claims. We will use reasonable efforts to notify you of any such claim, action, or proceeding which is subject to this indemnification upon becoming aware of it.</p>
|
||||
|
||||
<h2>User Data</h2>
|
||||
<p>We will maintain certain data that you transmit to the Site for the purpose of managing the performance of the Site, as well as data relating to your use of the Site. Although we perform regular routine backups of data, you are solely responsible for all data that you transmit or that relates to any activity you have undertaken using the Site. You agree that we shall have no liability to you for any loss or corruption of any such data, and you hereby waive any right of action against us arising from any such loss or corruption of such data.</p>
|
||||
|
||||
<h2>Miscellaneous</h2>
|
||||
<p>These Terms of Use and any policies or operating rules posted by us on the Site or in respect to the Site constitute the entire agreement and understanding between you and us. Our failure to exercise or enforce any right or provision of these Terms of Use shall not operate as a waiver of such right or provision. These Terms of Use operate to the fullest extent permissible by law. We may assign any or all of our rights and obligations to others at any time. We shall not be responsible or liable for any loss, damage, delay, or failure to act caused by any cause beyond our reasonable control. If any provision or part of a provision of these Terms of Use is determined to be unlawful, void, or unenforceable, that provision or part of the provision is deemed severable from these Terms of Use and does not affect the validity and enforceability of any remaining provisions. There is no joint venture, partnership, employment or agency relationship created between you and us as a result of these Terms of Use or use of the Site. You agree that these Terms of Use will not be construed against us by virtue of having drafted them. You hereby waive any and all defenses you may have based on the electronic form of these Terms of Use and the lack of signing by the parties hereto to execute these Terms of Use.</p>
|
||||
|
||||
<p><a [routerLink]="'.'" fragment="ref-a" id="comment-a">[a]</a> The DSpace software used to run this site is open source. Options for reuse and reproduction of the DSpace software is governed by its open source license: <a href="https://github.com/DSpace/DSpace/blob/main/LICENSE" target="_blank" rel="noopener noreferrer">https://github.com/DSpace/DSpace/blob/main/LICENSE</a></p>
|
@@ -1,37 +1,91 @@
|
||||
<h2>{{ 'info.privacy.head' | translate }}</h2>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Nunc sed velit dignissim sodales ut eu. In ante metus dictum at tempor. Diam phasellus vestibulum lorem sed risus. Sed cras ornare arcu dui vivamus. Sit amet consectetur adipiscing elit pellentesque. Id velit ut tortor pretium viverra suspendisse potenti. Sed euismod nisi porta lorem mollis aliquam ut. Justo laoreet sit amet cursus sit amet dictum sit. Ullamcorper morbi tincidunt ornare massa eget egestas.
|
||||
</p>
|
||||
<p>
|
||||
In iaculis nunc sed augue lacus. Curabitur vitae nunc sed velit dignissim sodales ut eu sem. Tellus id interdum velit laoreet id donec ultrices tincidunt arcu. Quis vel eros donec ac odio tempor. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae. Varius quam quisque id diam vel quam. Nisl tincidunt eget nullam non nisi est sit. Nunc aliquet bibendum enim facilisis. Aenean sed adipiscing diam donec adipiscing. Convallis tellus id interdum velit laoreet. Massa placerat duis ultricies lacus sed turpis tincidunt. Sed cras ornare arcu dui vivamus arcu. Egestas integer eget aliquet nibh praesent tristique. Sit amet purus gravida quis blandit turpis cursus in hac. Porta non pulvinar neque laoreet suspendisse. Quis risus sed vulputate odio ut. Dignissim enim sit amet venenatis urna cursus.
|
||||
</p>
|
||||
<p>
|
||||
Interdum velit laoreet id donec ultrices tincidunt arcu non sodales. Massa sapien faucibus et molestie. Dictumst vestibulum rhoncus est pellentesque elit ullamcorper. Metus dictum at tempor commodo ullamcorper. Tincidunt lobortis feugiat vivamus at augue eget. Non diam phasellus vestibulum lorem sed risus ultricies. Neque aliquam vestibulum morbi blandit cursus risus at ultrices mi. Euismod lacinia at quis risus sed. Lorem mollis aliquam ut porttitor leo a diam. Ipsum dolor sit amet consectetur. Ante in nibh mauris cursus mattis molestie a iaculis at. Commodo ullamcorper a lacus vestibulum. Pellentesque elit eget gravida cum sociis. Sit amet commodo nulla facilisi nullam vehicula. Vehicula ipsum a arcu cursus vitae congue mauris rhoncus aenean.
|
||||
</p>
|
||||
<p>
|
||||
Ac turpis egestas maecenas pharetra convallis. Lacus sed viverra tellus in. Nullam eget felis eget nunc lobortis mattis aliquam faucibus purus. Id aliquet risus feugiat in ante metus dictum at. Quis enim lobortis scelerisque fermentum dui faucibus. Eu volutpat odio facilisis mauris sit amet massa vitae tortor. Tellus elementum sagittis vitae et leo. Cras sed felis eget velit aliquet sagittis. Proin fermentum leo vel orci porta non pulvinar neque laoreet. Dui sapien eget mi proin sed libero enim. Ultrices mi tempus imperdiet nulla malesuada. Mattis molestie a iaculis at. Turpis massa sed elementum tempus egestas.
|
||||
</p>
|
||||
<p>
|
||||
Dui faucibus in ornare quam viverra orci sagittis eu volutpat. Cras adipiscing enim eu turpis. Ac felis donec et odio pellentesque. Iaculis nunc sed augue lacus viverra vitae congue eu consequat. Posuere lorem ipsum dolor sit amet consectetur adipiscing elit duis. Elit eget gravida cum sociis natoque penatibus. Id faucibus nisl tincidunt eget nullam non. Sagittis aliquam malesuada bibendum arcu vitae. Fermentum leo vel orci porta. Aliquam ultrices sagittis orci a scelerisque purus semper. Diam maecenas sed enim ut sem viverra aliquet eget sit. Et ultrices neque ornare aenean euismod. Eu mi bibendum neque egestas congue quisque egestas diam. Eget lorem dolor sed viverra. Ut lectus arcu bibendum at. Rutrum tellus pellentesque eu tincidunt tortor. Vitae congue eu consequat ac. Elit ullamcorper dignissim cras tincidunt. Sit amet volutpat consequat mauris nunc congue nisi.
|
||||
</p>
|
||||
<p>
|
||||
Cursus in hac habitasse platea dictumst quisque sagittis purus. Placerat duis ultricies lacus sed turpis tincidunt. In egestas erat imperdiet sed euismod nisi porta lorem mollis. Non nisi est sit amet facilisis magna. In massa tempor nec feugiat nisl pretium fusce. Pulvinar neque laoreet suspendisse interdum consectetur. Ullamcorper morbi tincidunt ornare massa eget egestas purus viverra accumsan. Fringilla urna porttitor rhoncus dolor purus non enim. Mauris nunc congue nisi vitae suscipit. Commodo elit at imperdiet dui accumsan sit amet nulla. Tempor id eu nisl nunc mi ipsum faucibus. Porta non pulvinar neque laoreet suspendisse. Nec nam aliquam sem et tortor consequat.
|
||||
</p>
|
||||
<p>
|
||||
Eget nunc lobortis mattis aliquam faucibus purus. Odio tempor orci dapibus ultrices. Sed nisi lacus sed viverra tellus. Elit ullamcorper dignissim cras tincidunt. Porttitor rhoncus dolor purus non enim praesent elementum facilisis. Viverra orci sagittis eu volutpat odio. Pharetra massa massa ultricies mi quis. Lectus vestibulum mattis ullamcorper velit sed ullamcorper. Pulvinar neque laoreet suspendisse interdum consectetur. Vitae auctor eu augue ut. Arcu dictum varius duis at consectetur lorem donec. Massa sed elementum tempus egestas sed sed. Risus viverra adipiscing at in tellus integer. Vulputate enim nulla aliquet porttitor lacus luctus accumsan. Pharetra massa massa ultricies mi. Elementum eu facilisis sed odio morbi quis commodo odio. Tincidunt lobortis feugiat vivamus at. Felis donec et odio pellentesque diam volutpat commodo sed. Risus feugiat in ante metus dictum at tempor commodo ullamcorper. Fringilla phasellus faucibus scelerisque eleifend donec pretium vulputate.
|
||||
</p>
|
||||
<p>
|
||||
Lectus proin nibh nisl condimentum id venenatis a condimentum. Id consectetur purus ut faucibus pulvinar elementum integer enim. Non pulvinar neque laoreet suspendisse interdum consectetur. Est pellentesque elit ullamcorper dignissim cras tincidunt lobortis feugiat vivamus. Suscipit tellus mauris a diam maecenas sed enim ut sem. Dolor purus non enim praesent elementum facilisis. Non enim praesent elementum facilisis leo vel. Ultricies leo integer malesuada nunc vel risus commodo viverra maecenas. Nulla porttitor massa id neque aliquam vestibulum. Erat velit scelerisque in dictum non consectetur. Amet cursus sit amet dictum. Nec tincidunt praesent semper feugiat nibh. Rutrum quisque non tellus orci ac auctor. Sagittis aliquam malesuada bibendum arcu vitae elementum. Massa tincidunt dui ut ornare lectus sit amet est. Aliquet porttitor lacus luctus accumsan tortor posuere ac. Quis hendrerit dolor magna eget est lorem ipsum dolor sit. Lectus mauris ultrices eros in.
|
||||
</p>
|
||||
<p>
|
||||
Massa massa ultricies mi quis hendrerit dolor magna. Est ullamcorper eget nulla facilisi etiam dignissim diam. Vulputate sapien nec sagittis aliquam malesuada. Nisi porta lorem mollis aliquam ut porttitor leo a diam. Tempus quam pellentesque nec nam. Faucibus vitae aliquet nec ullamcorper sit amet risus nullam eget. Gravida arcu ac tortor dignissim convallis aenean et tortor. A scelerisque purus semper eget duis at tellus at. Viverra ipsum nunc aliquet bibendum enim. Semper feugiat nibh sed pulvinar proin gravida hendrerit. Et ultrices neque ornare aenean euismod. Consequat semper viverra nam libero justo laoreet. Nunc mattis enim ut tellus elementum sagittis. Consectetur lorem donec massa sapien faucibus et. Vel risus commodo viverra maecenas accumsan lacus vel facilisis. Diam sollicitudin tempor id eu nisl nunc. Dolor magna eget est lorem ipsum dolor. Adipiscing elit pellentesque habitant morbi tristique.
|
||||
</p>
|
||||
<p>
|
||||
Nec sagittis aliquam malesuada bibendum arcu vitae elementum curabitur. Egestas fringilla phasellus faucibus scelerisque eleifend donec pretium vulputate sapien. Porttitor leo a diam sollicitudin tempor. Pellentesque dignissim enim sit amet venenatis urna cursus eget nunc. Posuere sollicitudin aliquam ultrices sagittis orci a scelerisque. Vehicula ipsum a arcu cursus vitae congue mauris rhoncus. Leo urna molestie at elementum. Duis tristique sollicitudin nibh sit amet commodo nulla facilisi. Libero id faucibus nisl tincidunt eget nullam. Tellus elementum sagittis vitae et leo duis ut diam. Sodales ut etiam sit amet nisl purus in mollis. Ipsum faucibus vitae aliquet nec ullamcorper sit amet risus. Lacus laoreet non curabitur gravida arcu ac tortor dignissim convallis. Aliquam malesuada bibendum arcu vitae elementum. Leo vel orci porta non pulvinar neque laoreet. Ipsum suspendisse ultrices gravida dictum fusce.
|
||||
</p>
|
||||
<p>
|
||||
Egestas erat imperdiet sed euismod nisi porta lorem. Venenatis a condimentum vitae sapien pellentesque habitant. Sit amet luctus venenatis lectus magna fringilla urna porttitor. Orci sagittis eu volutpat odio facilisis mauris sit amet massa. Ut enim blandit volutpat maecenas volutpat blandit aliquam. Libero volutpat sed cras ornare. Molestie ac feugiat sed lectus vestibulum mattis ullamcorper velit sed. Diam quis enim lobortis scelerisque fermentum dui. Pellentesque habitant morbi tristique senectus et netus. Auctor urna nunc id cursus metus aliquam eleifend. Elit scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique. Sed risus ultricies tristique nulla aliquet enim tortor. Tincidunt arcu non sodales neque sodales ut. Sed lectus vestibulum mattis ullamcorper velit sed ullamcorper morbi tincidunt.
|
||||
</p>
|
||||
<p>
|
||||
Pulvinar etiam non quam lacus suspendisse faucibus. Eu mi bibendum neque egestas congue. Egestas purus viverra accumsan in nisl nisi scelerisque eu. Vulputate enim nulla aliquet porttitor lacus luctus accumsan. Eu non diam phasellus vestibulum. Semper feugiat nibh sed pulvinar. Ante in nibh mauris cursus mattis molestie a. Maecenas accumsan lacus vel facilisis volutpat. Non quam lacus suspendisse faucibus. Quis commodo odio aenean sed adipiscing. Vel elit scelerisque mauris pellentesque pulvinar pellentesque habitant morbi. Sed cras ornare arcu dui vivamus arcu felis. Tortor vitae purus faucibus ornare suspendisse sed. Morbi tincidunt ornare massa eget egestas purus viverra. Nibh cras pulvinar mattis nunc. Luctus venenatis lectus magna fringilla urna porttitor. Enim blandit volutpat maecenas volutpat blandit aliquam etiam erat. Malesuada pellentesque elit eget gravida cum sociis natoque penatibus et. Felis eget nunc lobortis mattis aliquam faucibus purus in. Vivamus arcu felis bibendum ut.
|
||||
</p>
|
||||
<h1>{{ 'info.privacy.head' | translate }}</h1>
|
||||
<p><em>Last updated May 4, 2023</em></p>
|
||||
|
||||
<h2>Introduction</h2>
|
||||
<p>{{ 'repository.title' | translate }} ("Company" or "We") respects your privacy and is committed to protecting it through our compliance with this policy.</p>
|
||||
<p>This policy describes the types of information we may collect from you or that you may provide when you visit this website (our "Website") and our practices for collecting, using, maintaining, protecting, and disclosing that information.</p>
|
||||
<p>This policy applies to information we collect:</p>
|
||||
<ul>
|
||||
<li>On this Website.</li>
|
||||
<li>In email, text, and other electronic messages between you and this Website.</li>
|
||||
</ul>
|
||||
<p>It does not apply to information collected by:</p>
|
||||
<ul>
|
||||
<li>us offline or through any other means, including on any other website operated by Company or any third party; or</li>
|
||||
<li>any third party, including through any application or content (including advertising) that may link to or be accessible from or on the Website.</li>
|
||||
</ul>
|
||||
<p>Please read this policy carefully to understand our policies and practices regarding your information and how we will treat it. If you do not agree with our policies and practices, your choice is not to use our Website. By accessing or using this Website, you agree to this privacy policy. This policy may change from time to time. Your continued use of this Website after we make changes is deemed to be acceptance of those changes, so please check the policy periodically for updates.</p>
|
||||
|
||||
<h2>Children under the age of 13</h2>
|
||||
<p>Our Website is not intended for children under 13 years of age. No one under age 13 may provide any personal information to or on the Website. We do not knowingly collect personal information from children under 13. If you are under 13, do not use or provide any information on this Website or provide any information about yourself to us, including your name, address, telephone number, email address, or any screen name or username you may use. If we learn we have collected or received personal information from a child under 13 without verification of parental consent, we will delete that information.</p>
|
||||
|
||||
<h2>Information we collect about you and how we collect it</h2>
|
||||
<p>We collect several types of information from and about users of our Website, including information:</p>
|
||||
<ul>
|
||||
<li>by which you may be personally identified, such as name, e-mail address, telephone number, or any other identifier by which you may be contacted online or offline ("personal information"); and/or</li>
|
||||
<li>about your internet connection, the equipment you use to access our Website and usage details.</li>
|
||||
</ul>
|
||||
<p>We collect this information:</p>
|
||||
<ul>
|
||||
<li>directly from you when you provide it to us.</li>
|
||||
<li>automatically as you navigate through the site. Information collected automatically may include usage details, IP addresses, and information collected through cookies, web beacons, and other tracking technologies; and/or</li>
|
||||
<li>from third parties, for example, our business partners.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Information you provide to us</h2>
|
||||
<p>The information we collect on or through our Website may include:</p>
|
||||
<ul>
|
||||
<li>Information that you provide by filling in forms on our Website. We may also ask you for information when you report a problem with our Website.</li>
|
||||
<li>Records and copies of your correspondence (including email addresses), if you contact us.</li>
|
||||
<li>Your responses to surveys that we might ask you to complete for research purpose.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Information we collect through automatic data collection technologies</h2>
|
||||
<p>As you navigate through and interact with our Website, we may use automatic data collection technologies to collect certain information about your equipment, browsing actions, and patterns, including:</p>
|
||||
<ul>
|
||||
<li>Details of your visits to our Website, including traffic data, location data, logs, and other communication data and the resources that you access and use on the Website.</li>
|
||||
<li>Information about your computer and internet connection, including your IP address, operating system, and browser type.</li>
|
||||
</ul>
|
||||
<p>The information we collect automatically is statistical data and does not include personal information, but we may maintain it or associate it with personal information we collect in other ways or receive from third parties. It helps us to improve our Website and to deliver a better and more personalized service, including by enabling us to:</p>
|
||||
<ul>
|
||||
<li>Estimate our audience size and usage patterns.</li>
|
||||
<li>Store information about your preferences, allowing us to customize our Website according to your individual interests.</li>
|
||||
<li>Speed up your searches.</li>
|
||||
<li>Recognize you when you return to our Website.</li>
|
||||
</ul>
|
||||
<p>The technologies we use for this automatic data collection may include:</p>
|
||||
<ul>
|
||||
<li>Cookies (or browser cookies). A cookie is a small file placed on the hard drive of your computer. You may refuse to accept browser cookies by activating the appropriate setting on your browser. However, if you select this setting you may be unable to access certain parts of our Website. Unless you have adjusted your browser setting so that it will refuse cookies, our system will issue cookies when you direct your browser to our Website.</li>
|
||||
</ul>
|
||||
|
||||
<h2>How we use your information</h2>
|
||||
<p>We use information that we collect about you or that you provide to us, including any personal information:</p>
|
||||
<ul>
|
||||
<li>To present our Website and its contents to you.</li>
|
||||
<li>To provide you with information, products, or services that you request from us.</li>
|
||||
<li>To fulfill any other purpose for which you provide it.</li>
|
||||
<li>To carry out our obligations and enforce our rights arising from any contracts entered into between you and us, including for billing and collection.</li>
|
||||
<li>To notify you about changes to our Website or any products or services we offer or provide through it.</li>
|
||||
<li>In any other way we may describe when you provide the information.</li>
|
||||
<li>For any other purpose with your consent.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Disclosure of your information</h2>
|
||||
<p>We may disclose aggregated information about our users, and information that does not identify any individual, without restriction.</p>
|
||||
<p>We may disclose personal information that we collect or you provide as described in this privacy policy:</p>
|
||||
<ul>
|
||||
<li>To contractors, service providers, and other third parties we use to support our business and who are bound by contractual obligations to keep personal information confidential and use it only for the purposes for which we disclose it to them.</li>
|
||||
<li>To a buyer or other successor in the event of a merger, divestiture, restructuring, reorganization, dissolution, or other sale or transfer of some or all of Company's assets, whether as a going concern or as part of bankruptcy, liquidation, or similar proceeding, in which personal information held by Company about our Website users is among the assets transferred.</li>
|
||||
<li>To fulfill the purpose for which you provide it.</li>
|
||||
<li>For any other purpose disclosed by us when you provide the information.</li>
|
||||
<li>With your consent.</li>
|
||||
</ul>
|
||||
<p>We may also disclose your personal information:</p>
|
||||
<ul>
|
||||
<li>To comply with any court order, law, or legal process, including to respond to any government or regulatory request.</li>
|
||||
<li>To enforce or apply our <a [routerLink]="['/info/end-user-agreement']" target="_blank" rel="noopener noreferrer">End User Agreement</a> and other agreements, including for billing and collection purposes.</li>
|
||||
<li>If we believe disclosure is necessary or appropriate to protect the rights, property, or safety of the Company, our customers, or others.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Changes to our privacy policy</h2>
|
||||
<p>It is our policy to post any changes we make to our privacy policy on this page. The date the privacy policy was last revised is identified at the top of the page. You are responsible for periodically visiting our Website and this privacy policy to check for any changes.</p>
|
@@ -8,10 +8,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="message">{{ 'grant-deny-request-copy.email.message' | translate }}</label>
|
||||
<textarea class="form-control" id="message" rows="8" [ngClass]="{'is-invalid': !message || message.length === 0}" [(ngModel)]="message" name="message"></textarea>
|
||||
<div *ngIf="!message || message.length === 0" class="invalid-feedback">
|
||||
{{ 'grant-deny-request-copy.email.message.empty' | translate }}
|
||||
</div>
|
||||
<textarea class="form-control" id="message" rows="8" [(ngModel)]="message" name="message"></textarea>
|
||||
</div>
|
||||
<ng-content></ng-content>
|
||||
<div class="d-flex flex-row-reverse">
|
||||
|
@@ -123,19 +123,6 @@ describe('GrantRequestCopyComponent', () => {
|
||||
spyOn(translateService, 'get').and.returnValue(observableOf('translated-message'));
|
||||
});
|
||||
|
||||
it('message$ should be parameterized correctly', (done) => {
|
||||
component.message$.subscribe(() => {
|
||||
expect(translateService.get).toHaveBeenCalledWith(jasmine.anything(), Object.assign({
|
||||
recipientName: itemRequest.requestName,
|
||||
itemUrl: itemUrl,
|
||||
itemName: itemName,
|
||||
authorName: user.name,
|
||||
authorEmail: user.email,
|
||||
}));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('grant', () => {
|
||||
let email: RequestCopyEmail;
|
||||
|
||||
|
@@ -9,12 +9,6 @@ import {
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { AuthService } from '../../core/auth/auth.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { combineLatest as observableCombineLatest } from 'rxjs';
|
||||
import { ItemDataService } from '../../core/data/item-data.service';
|
||||
import { EPerson } from '../../core/eperson/models/eperson.model';
|
||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { isNotEmpty } from '../../shared/empty.util';
|
||||
import { ItemRequestDataService } from '../../core/data/item-request-data.service';
|
||||
import { RequestCopyEmail } from '../email-request-copy/request-copy-email.model';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
@@ -54,8 +48,6 @@ export class GrantRequestCopyComponent implements OnInit {
|
||||
private route: ActivatedRoute,
|
||||
private authService: AuthService,
|
||||
private translateService: TranslateService,
|
||||
private itemDataService: ItemDataService,
|
||||
private nameService: DSONameService,
|
||||
private itemRequestService: ItemRequestDataService,
|
||||
private notificationsService: NotificationsService,
|
||||
) {
|
||||
@@ -69,31 +61,7 @@ export class GrantRequestCopyComponent implements OnInit {
|
||||
redirectOn4xx(this.router, this.authService),
|
||||
);
|
||||
|
||||
const msgParams$ = observableCombineLatest([
|
||||
this.itemRequestRD$.pipe(getFirstSucceededRemoteDataPayload()),
|
||||
this.authService.getAuthenticatedUserFromStore(),
|
||||
]).pipe(
|
||||
switchMap(([itemRequest, user]: [ItemRequest, EPerson]) => {
|
||||
return this.itemDataService.findById(itemRequest.itemId).pipe(
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
map((item: Item) => {
|
||||
const uri = item.firstMetadataValue('dc.identifier.uri');
|
||||
return Object.assign({
|
||||
recipientName: itemRequest.requestName,
|
||||
itemUrl: isNotEmpty(uri) ? uri : item.handle,
|
||||
itemName: this.nameService.getName(item),
|
||||
authorName: this.nameService.getName(user),
|
||||
authorEmail: user.email,
|
||||
});
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
this.subject$ = this.translateService.get('grant-request-copy.email.subject');
|
||||
this.message$ = msgParams$.pipe(
|
||||
switchMap((params) => this.translateService.get('grant-request-copy.email.message', params)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -13,22 +13,20 @@
|
||||
(ngbEvent)="onCustomEvent($event)">
|
||||
<ng-template modelType="ARRAY" let-group let-index="index" let-context="context">
|
||||
<!--Array with repeatable items-->
|
||||
<div *ngIf="(!context.notRepeatable) && !isVirtual(context, index)"
|
||||
<div *ngIf="(!context.notRepeatable) && !isVirtual(context, index) && group.context.groups.length !== 1 && !isItemReadOnly(context, index)"
|
||||
class="col-xs-2 d-flex flex-column justify-content-sm-start align-items-end">
|
||||
<button type="button" class="btn btn-secondary" role="button"
|
||||
title="{{'form.remove' | translate}}"
|
||||
attr.aria-label="{{'form.remove' | translate}}"
|
||||
(click)="removeItem($event, context, index)"
|
||||
[disabled]="group.context.groups.length === 1 || isItemReadOnly(context, index)">
|
||||
(click)="removeItem($event, context, index)">
|
||||
<span><i class="fas fa-trash" aria-hidden="true"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="(!context.notRepeatable) && index === (group.context.groups.length - 1)" class="clearfix pl-4 w-100">
|
||||
<div *ngIf="(!context.notRepeatable) && index === (group.context.groups.length - 1) && !isItemReadOnly(context, index)" class="clearfix pl-4 w-100">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" role="button" class="ds-form-add-more btn btn-link"
|
||||
title="{{'form.add' | translate}}"
|
||||
attr.aria-label="{{'form.add' | translate}}"
|
||||
[disabled]="isItemReadOnly(context, index)"
|
||||
(click)="insertItem($event, group.context, group.context.groups.length)">
|
||||
<span><i class="fas fa-plus"></i> {{'form.add' | translate}}</span>
|
||||
</button>
|
||||
|
@@ -4,6 +4,7 @@
|
||||
<span *ngIf="linkType == linkTypes.None" class="lead">
|
||||
{{ dsoNameService.getName(object) }}
|
||||
</span>
|
||||
<span *ngIf="object.archivedItemsCount >= 0" class="lead archived-items-lead">[{{object.archivedItemsCount}}]</span>
|
||||
<div *ngIf="object.shortDescription" class="text-muted abstract-text">
|
||||
{{object.shortDescription}}
|
||||
</div>
|
||||
|
@@ -9,6 +9,29 @@ import { DSONameServiceMock } from '../../mocks/dso-name.service.mock';
|
||||
let collectionListElementComponent: CollectionListElementComponent;
|
||||
let fixture: ComponentFixture<CollectionListElementComponent>;
|
||||
|
||||
const mockCollectionWithArchivedItems: Collection = Object.assign(new Collection(), {
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{
|
||||
language: 'en_US',
|
||||
value: 'Test title'
|
||||
}
|
||||
]
|
||||
}, archivedItemsCount: 1
|
||||
});
|
||||
|
||||
const mockCollectionWithArchivedItemsDisabledAtBackend: Collection = Object.assign(new Collection(), {
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{
|
||||
language: 'en_US',
|
||||
value: 'Test title'
|
||||
}
|
||||
]
|
||||
}, archivedItemsCount: -1
|
||||
});
|
||||
|
||||
|
||||
const mockCollectionWithAbstract: Collection = Object.assign(new Collection(), {
|
||||
metadata: {
|
||||
'dc.description.abstract': [
|
||||
@@ -17,7 +40,7 @@ const mockCollectionWithAbstract: Collection = Object.assign(new Collection(), {
|
||||
value: 'Short description'
|
||||
}
|
||||
]
|
||||
}
|
||||
}, archivedItemsCount: 1
|
||||
});
|
||||
|
||||
const mockCollectionWithoutAbstract: Collection = Object.assign(new Collection(), {
|
||||
@@ -28,7 +51,7 @@ const mockCollectionWithoutAbstract: Collection = Object.assign(new Collection()
|
||||
value: 'Test title'
|
||||
}
|
||||
]
|
||||
}
|
||||
}, archivedItemsCount: 1
|
||||
});
|
||||
|
||||
describe('CollectionListElementComponent', () => {
|
||||
@@ -74,4 +97,29 @@ describe('CollectionListElementComponent', () => {
|
||||
expect(collectionAbstractField).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('When the collection has archived items', () => {
|
||||
beforeEach(() => {
|
||||
collectionListElementComponent.object = mockCollectionWithArchivedItems;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show the archived items paragraph', () => {
|
||||
const field = fixture.debugElement.query(By.css('span.archived-items-lead'));
|
||||
expect(field).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('When the collection archived items are disabled at backend', () => {
|
||||
beforeEach(() => {
|
||||
collectionListElementComponent.object = mockCollectionWithArchivedItemsDisabledAtBackend;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should not show the archived items paragraph', () => {
|
||||
const field = fixture.debugElement.query(By.css('span.archived-items-lead'));
|
||||
expect(field).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -4,6 +4,7 @@
|
||||
<span *ngIf="linkType == linkTypes.None" class="lead">
|
||||
{{ dsoNameService.getName(object) }}
|
||||
</span>
|
||||
<span *ngIf="object.archivedItemsCount >= 0" class="lead archived-items-lead">[{{object.archivedItemsCount}}]</span>
|
||||
<div *ngIf="object.shortDescription" class="text-muted abstract-text">
|
||||
{{object.shortDescription}}
|
||||
</div>
|
||||
|
@@ -22,10 +22,10 @@
|
||||
<span>
|
||||
<i class="fas fa-upload" aria-hidden="true"></i>
|
||||
{{dropMsg | translate}}{{'uploader.or' | translate}}
|
||||
<label for="inputFileUploader" class="btn btn-link m-0 p-0 ml-1" tabindex="0" (keyup.enter)="$event.stopImmediatePropagation(); fileInput.click()">
|
||||
<label for="inputFileUploader-{{uploaderId}}" class="btn btn-link m-0 p-0 ml-1" tabindex="0" (keyup.enter)="$event.stopImmediatePropagation(); fileInput.click()">
|
||||
<span role="button" [attr.aria-label]="'uploader.browse' | translate">{{'uploader.browse' | translate}}</span>
|
||||
</label>
|
||||
<input #fileInput id="inputFileUploader" class="d-none" type="file" ng2FileSelect [uploader]="uploader" multiple tabindex="0" />
|
||||
<input #fileInput id="inputFileUploader-{{uploaderId}}" class="d-none" type="file" ng2FileSelect [uploader]="uploader" multiple tabindex="0" />
|
||||
</span>
|
||||
</div>
|
||||
<div *ngIf="(isOverBaseDropZone | async) || uploader?.queue?.length !== 0">
|
||||
|
@@ -294,7 +294,9 @@ describe('SubmissionSectionFormComponent test suite', () => {
|
||||
'dc.title': [new FormFieldMetadataValueObject('test')]
|
||||
};
|
||||
compAsAny.formData = {};
|
||||
compAsAny.sectionMetadata = ['dc.title'];
|
||||
compAsAny.sectionData.data = {
|
||||
'dc.title': [new FormFieldMetadataValueObject('test')]
|
||||
};
|
||||
spyOn(compAsAny, 'inCurrentSubmissionScope').and.callThrough();
|
||||
|
||||
expect(comp.hasMetadataEnrichment(newSectionData)).toBeTruthy();
|
||||
@@ -306,7 +308,9 @@ describe('SubmissionSectionFormComponent test suite', () => {
|
||||
'dc.title': [new FormFieldMetadataValueObject('test')]
|
||||
};
|
||||
compAsAny.formData = newSectionData;
|
||||
compAsAny.sectionMetadata = ['dc.title'];
|
||||
compAsAny.sectionData.data = {
|
||||
'dc.title': [new FormFieldMetadataValueObject('test')]
|
||||
};
|
||||
spyOn(compAsAny, 'inCurrentSubmissionScope').and.callThrough();
|
||||
|
||||
expect(comp.hasMetadataEnrichment(newSectionData)).toBeFalsy();
|
||||
@@ -343,6 +347,22 @@ describe('SubmissionSectionFormComponent test suite', () => {
|
||||
} as FormFieldModel
|
||||
]
|
||||
},
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
selectableMetadata: [{ metadata: 'scoped.workflow.relation' }],
|
||||
scope: 'WORKFLOW',
|
||||
} as FormFieldModel,
|
||||
],
|
||||
},
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
selectableMetadata: [{ metadata: 'scoped.workspace.relation' }],
|
||||
scope: 'WORKSPACE',
|
||||
} as FormFieldModel,
|
||||
],
|
||||
},
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
@@ -371,6 +391,14 @@ describe('SubmissionSectionFormComponent test suite', () => {
|
||||
it('should return false for fields scoped to workflow', () => {
|
||||
expect((comp as any).inCurrentSubmissionScope('scoped.workflow')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for relation fields scoped to workspace', () => {
|
||||
expect((comp as any).inCurrentSubmissionScope('scoped.workspace.relation')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for relation fields scoped to workflow', () => {
|
||||
expect((comp as any).inCurrentSubmissionScope('scoped.workflow.relation')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('in workflow scope', () => {
|
||||
@@ -390,6 +418,14 @@ describe('SubmissionSectionFormComponent test suite', () => {
|
||||
it('should return false for fields scoped to workspace', () => {
|
||||
expect((comp as any).inCurrentSubmissionScope('scoped.workspace')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for relation fields scoped to workflow', () => {
|
||||
expect((comp as any).inCurrentSubmissionScope('scoped.workflow.relation')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for relation fields scoped to workspace', () => {
|
||||
expect((comp as any).inCurrentSubmissionScope('scoped.workspace.relation')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -39,6 +39,7 @@ import { WorkflowItem } from '../../../core/submission/models/workflowitem.model
|
||||
import { SubmissionObject } from '../../../core/submission/models/submission-object.model';
|
||||
import { SubmissionSectionObject } from '../../objects/submission-section-object.model';
|
||||
import { SubmissionSectionError } from '../../objects/submission-section-error.model';
|
||||
import { FormRowModel } from '../../../core/config/models/config-submission-form.model';
|
||||
|
||||
/**
|
||||
* This component represents a section that contains a Form.
|
||||
@@ -228,7 +229,9 @@ export class SubmissionSectionFormComponent extends SectionModelComponent {
|
||||
|
||||
const sectionDataToCheck = {};
|
||||
Object.keys(sectionData).forEach((key) => {
|
||||
if (this.sectionMetadata && this.sectionMetadata.includes(key) && this.inCurrentSubmissionScope(key)) {
|
||||
// todo: removing Relationships works due to a bug -- dspace.entity.type is included in sectionData, which is what triggers the update;
|
||||
// if we use this.sectionMetadata.includes(key), this field is filtered out and removed Relationships won't disappear from the form.
|
||||
if (this.inCurrentSubmissionScope(key)) {
|
||||
sectionDataToCheck[key] = sectionData[key];
|
||||
}
|
||||
});
|
||||
@@ -256,9 +259,15 @@ export class SubmissionSectionFormComponent extends SectionModelComponent {
|
||||
* @private
|
||||
*/
|
||||
private inCurrentSubmissionScope(field: string): boolean {
|
||||
const scope = this.formConfig?.rows.find(row => {
|
||||
return row.fields?.[0]?.selectableMetadata?.[0]?.metadata === field;
|
||||
}).fields?.[0]?.scope;
|
||||
const scope = this.formConfig?.rows.find((row: FormRowModel) => {
|
||||
if (row.fields?.[0]?.selectableMetadata) {
|
||||
return row.fields?.[0]?.selectableMetadata?.[0]?.metadata === field;
|
||||
} else if (row.fields?.[0]?.selectableRelationship) {
|
||||
return row.fields?.[0]?.selectableRelationship.relationshipType === field.replace(/^relationship\./g, '');
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
})?.fields?.[0]?.scope;
|
||||
|
||||
switch (scope) {
|
||||
case SubmissionScopeType.WorkspaceItem: {
|
||||
|
@@ -262,7 +262,7 @@
|
||||
"admin.registries.schema.fields.table.id": "ID",
|
||||
|
||||
// "admin.registries.schema.fields.table.scopenote": "Scope Note",
|
||||
"admin.registries.schema.fields.table.scopenote": "Gültigkeitsbereich",
|
||||
"admin.registries.schema.fields.table.scopenote": "Geltungs- bzw. Gültigkeitsbereich",
|
||||
|
||||
// "admin.registries.schema.form.create": "Create metadata field",
|
||||
"admin.registries.schema.form.create": "Metadatenfeld anlegen",
|
||||
@@ -277,7 +277,7 @@
|
||||
"admin.registries.schema.form.qualifier": "Qualifizierer",
|
||||
|
||||
// "admin.registries.schema.form.scopenote": "Scope Note",
|
||||
"admin.registries.schema.form.scopenote": "Geltungsbereich",
|
||||
"admin.registries.schema.form.scopenote": "Geltungs- bzw. Gültigkeitsbereich",
|
||||
|
||||
// "admin.registries.schema.head": "Metadata Schema",
|
||||
"admin.registries.schema.head": "Metadatenschema",
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1922,6 +1922,9 @@
|
||||
// "home.breadcrumbs": "Home",
|
||||
"home.breadcrumbs": "Etusivu",
|
||||
|
||||
// "home.search-form.placeholder": "Search the repository ...",
|
||||
"home.search-form.placeholder": "Hae julkaisuarkistosta ...",
|
||||
|
||||
// "home.title": "DSpace Angular :: Home",
|
||||
"home.title": "DSpace Angular :: Etusivu",
|
||||
|
||||
@@ -4020,6 +4023,8 @@
|
||||
// "search.breadcrumbs": "Search",
|
||||
"search.breadcrumbs": "Hae",
|
||||
|
||||
// "search.search-form.placeholder": "Search the repository ...",
|
||||
"search.search-form.placeholder": "Hae julkaisuarkistosta ...",
|
||||
|
||||
// "search.filters.applied.f.author": "Author",
|
||||
"search.filters.applied.f.author": "Tekijä",
|
||||
|
@@ -13,6 +13,8 @@ export interface CacheConfig extends Config {
|
||||
serverSide: {
|
||||
// Debug server-side caching. Set to true to see cache hits/misses/refreshes in console logs.
|
||||
debug: boolean,
|
||||
// List of response headers to save into the cache
|
||||
headers: string[],
|
||||
// Cache specific to known bots. Allows you to serve cached contents to bots only.
|
||||
botCache: {
|
||||
// Maximum number of pages (rendered via SSR) to cache. Setting max=0 disables the cache.
|
||||
|
@@ -78,6 +78,8 @@ export class DefaultAppConfig implements AppConfig {
|
||||
// In-memory cache of server-side rendered content
|
||||
serverSide: {
|
||||
debug: false,
|
||||
// Link header is used for signposting functionality
|
||||
headers: ['Link'],
|
||||
// Cache specific to known bots. Allows you to serve cached contents to bots only.
|
||||
// Defaults to caching 1,000 pages. Each page expires after 1 day
|
||||
botCache: {
|
||||
|
@@ -59,6 +59,7 @@ export const environment: BuildConfig = {
|
||||
// In-memory cache of server-side rendered pages. Disabled in test environment (max=0)
|
||||
serverSide: {
|
||||
debug: false,
|
||||
headers: ['Link'],
|
||||
botCache: {
|
||||
max: 0,
|
||||
timeToLive: 24 * 60 * 60 * 1000, // 1 day
|
||||
|
Reference in New Issue
Block a user