diff --git a/README.md b/README.md index 78d7816f65..5d8a323461 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ DSPACE_REST_SSL # Whether the angular REST uses SSL [true/false] The same settings can also be overwritten by setting system environment variables instead, E.g.: ```bash -export DSPACE_HOST=https://dspace7.4science.cloud/server +export DSPACE_HOST=dspace7.4science.cloud ``` The priority works as follows: **environment variable** overrides **variable in `.env` file** overrides **`environment.(prod, dev or test).ts`** overrides **`environment.common.ts`** diff --git a/package.json b/package.json index 8d3c936212..21a89400bf 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "moment": "^2.22.1", "morgan": "^1.9.1", "ng-mocks": "^8.1.0", - "ng2-file-upload": "1.2.1", + "ng2-file-upload": "1.4.0", "ng2-nouislider": "^1.8.2", "ngx-bootstrap": "^5.3.2", "ngx-infinite-scroll": "6.0.1", diff --git a/src/app/+admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.spec.ts b/src/app/+admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.spec.ts index 98128a6a61..0cb259c6d1 100644 --- a/src/app/+admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.spec.ts +++ b/src/app/+admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.spec.ts @@ -27,7 +27,8 @@ describe('MetadataFieldFormComponent', () => { /* tslint:disable:no-empty */ const registryServiceStub = { getActiveMetadataField: () => observableOf(undefined), - createOrUpdateMetadataField: (field: MetadataField) => observableOf(field), + createMetadataField: (field: MetadataField) => observableOf(field), + updateMetadataField: (field: MetadataField) => observableOf(field), cancelEditMetadataField: () => {}, cancelEditMetadataSchema: () => {}, clearMetadataFieldRequests: () => observableOf(undefined) @@ -75,7 +76,6 @@ describe('MetadataFieldFormComponent', () => { const scopeNote = 'fakeScopeNote'; const expected = Object.assign(new MetadataField(), { - schema: metadataSchema, element: element, qualifier: qualifier, scopeNote: scopeNote diff --git a/src/app/+admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts b/src/app/+admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts index 42f6441791..70cdc15b4d 100644 --- a/src/app/+admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts +++ b/src/app/+admin/admin-registries/metadata-schema/metadata-field-form/metadata-field-form.component.ts @@ -157,19 +157,17 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy { this.registryService.getActiveMetadataField().pipe(take(1)).subscribe( (field) => { const values = { - schema: this.metadataSchema, element: this.element.value, qualifier: this.qualifier.value, scopeNote: this.scopeNote.value }; if (field == null) { - this.registryService.createOrUpdateMetadataField(Object.assign(new MetadataField(), values)).subscribe((newField) => { + this.registryService.createMetadataField(Object.assign(new MetadataField(), values), this.metadataSchema).subscribe((newField) => { this.submitForm.emit(newField); }); } else { - this.registryService.createOrUpdateMetadataField(Object.assign(new MetadataField(), field, { + this.registryService.updateMetadataField(Object.assign(new MetadataField(), field, { id: field.id, - schema: this.metadataSchema, element: (values.element ? values.element : field.element), qualifier: (values.qualifier ? values.qualifier : field.qualifier), scopeNote: (values.scopeNote ? values.scopeNote : field.scopeNote) diff --git a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.spec.ts b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.spec.ts index 26f4c90b0c..9532a64e84 100644 --- a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.spec.ts +++ b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.spec.ts @@ -61,7 +61,7 @@ describe('MetadataSchemaComponent', () => { element: 'contributor', qualifier: 'advisor', scopeNote: null, - schema: mockSchemasList[0] + schema: createSuccessfulRemoteDataObject$(mockSchemasList[0]) }, { id: 2, @@ -73,7 +73,7 @@ describe('MetadataSchemaComponent', () => { element: 'contributor', qualifier: 'author', scopeNote: null, - schema: mockSchemasList[0] + schema: createSuccessfulRemoteDataObject$(mockSchemasList[0]) }, { id: 3, @@ -85,7 +85,7 @@ describe('MetadataSchemaComponent', () => { element: 'contributor', qualifier: 'editor', scopeNote: 'test scope note', - schema: mockSchemasList[1] + schema: createSuccessfulRemoteDataObject$(mockSchemasList[1]) }, { id: 4, @@ -97,15 +97,15 @@ describe('MetadataSchemaComponent', () => { element: 'contributor', qualifier: 'illustrator', scopeNote: null, - schema: mockSchemasList[1] + schema: createSuccessfulRemoteDataObject$(mockSchemasList[1]) } ]; const mockSchemas = createSuccessfulRemoteDataObject$(new PaginatedList(null, mockSchemasList)); /* tslint:disable:no-empty */ const registryServiceStub = { getMetadataSchemas: () => mockSchemas, - getMetadataFieldsBySchema: (schema: MetadataSchema) => createSuccessfulRemoteDataObject$(new PaginatedList(null, mockFieldsList.filter((value) => value.schema === schema))), - getMetadataSchemaByName: (schemaName: string) => createSuccessfulRemoteDataObject$(mockSchemasList.filter((value) => value.prefix === schemaName)[0]), + getMetadataFieldsBySchema: (schema: MetadataSchema) => createSuccessfulRemoteDataObject$(new PaginatedList(null, mockFieldsList.filter((value) => value.id === 3 || value.id === 4))), + getMetadataSchemaByPrefix: (schemaName: string) => createSuccessfulRemoteDataObject$(mockSchemasList.filter((value) => value.prefix === schemaName)[0]), getActiveMetadataField: () => observableOf(undefined), getSelectedMetadataFields: () => observableOf([]), editMetadataField: (schema) => {}, diff --git a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.ts b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.ts index 96872573b9..87f4b863c0 100644 --- a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.ts +++ b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.ts @@ -17,6 +17,7 @@ import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operato import { toFindListOptions } from '../../../shared/pagination/pagination.utils'; import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; import { combineLatest } from 'rxjs/internal/observable/combineLatest'; +import { followLink } from '../../../shared/utils/follow-link-config.model'; @Component({ selector: 'ds-metadata-schema', @@ -71,7 +72,7 @@ export class MetadataSchemaComponent implements OnInit { * @param params */ initialize(params) { - this.metadataSchema$ = this.registryService.getMetadataSchemaByName(params.schemaName).pipe(getFirstSucceededRemoteDataPayload()); + this.metadataSchema$ = this.registryService.getMetadataSchemaByPrefix(params.schemaName).pipe(getFirstSucceededRemoteDataPayload()); this.updateFields(); } @@ -91,7 +92,7 @@ export class MetadataSchemaComponent implements OnInit { this.metadataFields$ = combineLatest(this.metadataSchema$, this.needsUpdate$).pipe( switchMap(([schema, update]: [MetadataSchema, boolean]) => { if (update) { - return this.registryService.getMetadataFieldsBySchema(schema, toFindListOptions(this.config)); + return this.registryService.getMetadataFieldsBySchema(schema, toFindListOptions(this.config), followLink('schema')); } }) ); diff --git a/src/app/+admin/admin-sidebar/admin-sidebar.component.ts b/src/app/+admin/admin-sidebar/admin-sidebar.component.ts index a2818b699a..e5a71aa411 100644 --- a/src/app/+admin/admin-sidebar/admin-sidebar.component.ts +++ b/src/app/+admin/admin-sidebar/admin-sidebar.component.ts @@ -149,11 +149,17 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { this.modalService.open(CreateItemParentSelectorComponent); } } as OnClickMenuItemModel, - // model: { - // type: MenuItemType.LINK, - // text: 'menu.section.new_item', - // link: '/submit' - // } as LinkMenuItemModel, + }, + { + id: 'new_process', + parentID: 'new', + active: false, + visible: true, + model: { + type: MenuItemType.LINK, + text: 'menu.section.new_process', + link: '/processes/new' + } as LinkMenuItemModel, }, { id: 'new_item_version', @@ -350,6 +356,20 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { icon: 'cogs', index: 9 }, + + /* Processes */ + { + id: 'processes', + active: false, + visible: true, + model: { + type: MenuItemType.LINK, + text: 'menu.section.processes', + link: '/processes' + } as LinkMenuItemModel, + icon: 'terminal', + index: 10 + }, ]; menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, Object.assign(menuSection, { shouldPersistOnRouteChange: true @@ -466,7 +486,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { link: '/admin/workflow' } as LinkMenuItemModel, icon: 'user-check', - index: 10 + index: 11 }, ]; diff --git a/src/app/+collection-page/collection-page-routing.module.ts b/src/app/+collection-page/collection-page-routing.module.ts index 589defa331..1ebd9b3630 100644 --- a/src/app/+collection-page/collection-page-routing.module.ts +++ b/src/app/+collection-page/collection-page-routing.module.ts @@ -9,10 +9,13 @@ import { CreateCollectionPageGuard } from './create-collection-page/create-colle import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component'; import { URLCombiner } from '../core/url-combiner/url-combiner'; import { getCollectionModulePath } from '../app-routing.module'; +import { EditItemTemplatePageComponent } from './edit-item-template-page/edit-item-template-page.component'; +import { ItemTemplatePageResolver } from './edit-item-template-page/item-template-page.resolver'; import { CollectionItemMapperComponent } from './collection-item-mapper/collection-item-mapper.component'; import { CollectionBreadcrumbResolver } from '../core/breadcrumbs/collection-breadcrumb.resolver'; import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service'; import { LinkService } from '../core/cache/builders/link.service'; +import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; export const COLLECTION_PARENT_PARAMETER = 'parent'; @@ -30,6 +33,7 @@ export function getCollectionCreatePath() { const COLLECTION_CREATE_PATH = 'create'; const COLLECTION_EDIT_PATH = 'edit'; +const ITEMTEMPLATE_PATH = 'itemtemplate'; @NgModule({ imports: [ @@ -58,6 +62,16 @@ const COLLECTION_EDIT_PATH = 'edit'; component: DeleteCollectionPageComponent, canActivate: [AuthenticatedGuard], }, + { + path: ITEMTEMPLATE_PATH, + component: EditItemTemplatePageComponent, + canActivate: [AuthenticatedGuard], + resolve: { + item: ItemTemplatePageResolver, + breadcrumb: I18nBreadcrumbResolver + }, + data: { title: 'collection.edit.template.title', breadcrumbKey: 'collection.edit.template' } + }, { path: '', component: CollectionPageComponent, @@ -75,6 +89,7 @@ const COLLECTION_EDIT_PATH = 'edit'; ], providers: [ CollectionPageResolver, + ItemTemplatePageResolver, CollectionBreadcrumbResolver, DSOBreadcrumbsService, LinkService, diff --git a/src/app/+collection-page/collection-page.module.ts b/src/app/+collection-page/collection-page.module.ts index 03daae68ae..554d56815a 100644 --- a/src/app/+collection-page/collection-page.module.ts +++ b/src/app/+collection-page/collection-page.module.ts @@ -8,6 +8,8 @@ import { CollectionPageRoutingModule } from './collection-page-routing.module'; import { CreateCollectionPageComponent } from './create-collection-page/create-collection-page.component'; import { CollectionFormComponent } from './collection-form/collection-form.component'; import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component'; +import { EditItemTemplatePageComponent } from './edit-item-template-page/edit-item-template-page.component'; +import { EditItemPageModule } from '../+item-page/edit-item-page/edit-item-page.module'; import { CollectionItemMapperComponent } from './collection-item-mapper/collection-item-mapper.component'; import { SearchService } from '../core/shared/search/search.service'; import { StatisticsModule } from '../statistics/statistics.module'; @@ -17,13 +19,15 @@ import { StatisticsModule } from '../statistics/statistics.module'; CommonModule, SharedModule, CollectionPageRoutingModule, - StatisticsModule.forRoot() + StatisticsModule.forRoot(), + EditItemPageModule ], declarations: [ CollectionPageComponent, CreateCollectionPageComponent, DeleteCollectionPageComponent, CollectionFormComponent, + EditItemTemplatePageComponent, CollectionItemMapperComponent ], exports: [ diff --git a/src/app/+collection-page/edit-collection-page/collection-metadata/collection-metadata.component.html b/src/app/+collection-page/edit-collection-page/collection-metadata/collection-metadata.component.html index 6f3a63790d..b4eaf46bfb 100644 --- a/src/app/+collection-page/edit-collection-page/collection-metadata/collection-metadata.component.html +++ b/src/app/+collection-page/edit-collection-page/collection-metadata/collection-metadata.component.html @@ -1,3 +1,21 @@ +
+ +
+ + + +
+
diff --git a/src/app/+collection-page/edit-collection-page/collection-metadata/collection-metadata.component.spec.ts b/src/app/+collection-page/edit-collection-page/collection-metadata/collection-metadata.component.spec.ts index 6b480c1a79..aec843afa3 100644 --- a/src/app/+collection-page/edit-collection-page/collection-metadata/collection-metadata.component.spec.ts +++ b/src/app/+collection-page/edit-collection-page/collection-metadata/collection-metadata.component.spec.ts @@ -4,16 +4,54 @@ import { SharedModule } from '../../../shared/shared.module'; import { CommonModule } from '@angular/common'; import { RouterTestingModule } from '@angular/router/testing'; import { CollectionDataService } from '../../../core/data/collection-data.service'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { of as observableOf } from 'rxjs/internal/observable/of'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { CollectionMetadataComponent } from './collection-metadata.component'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; -import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; +import { Item } from '../../../core/shared/item.model'; +import { ItemTemplateDataService } from '../../../core/data/item-template-data.service'; +import { Collection } from '../../../core/shared/collection.model'; +import { ObjectCacheService } from '../../../core/cache/object-cache.service'; +import { RequestService } from '../../../core/data/request.service'; +import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; describe('CollectionMetadataComponent', () => { let comp: CollectionMetadataComponent; let fixture: ComponentFixture; + let router: Router; + let itemTemplateService: ItemTemplateDataService; + + const template = Object.assign(new Item(), { + _links: { + self: { href: 'template-selflink' } + } + }); + const collection = Object.assign(new Collection(), { + uuid: 'collection-id', + id: 'collection-id', + name: 'Fake Collection', + _links: { + self: { href: 'collection-selflink' } + } + }); + + const itemTemplateServiceStub = Object.assign({ + findByCollectionID: () => createSuccessfulRemoteDataObject$(template), + create: () => createSuccessfulRemoteDataObject$(template), + deleteByCollectionID: () => observableOf(true) + }); + + const notificationsService = jasmine.createSpyObj('notificationsService', { + success: {}, + error: {} + }); + const objectCache = jasmine.createSpyObj('objectCache', { + remove: {} + }); + const requestService = jasmine.createSpyObj('requestService', { + removeByHrefSubstring: {} + }); beforeEach(async(() => { TestBed.configureTestingModule({ @@ -21,8 +59,11 @@ describe('CollectionMetadataComponent', () => { declarations: [CollectionMetadataComponent], providers: [ { provide: CollectionDataService, useValue: {} }, - { provide: ActivatedRoute, useValue: { parent: { data: observableOf({ dso: { payload: {} } }) } } }, - { provide: NotificationsService, useValue: new NotificationsServiceStub() } + { provide: ItemTemplateDataService, useValue: itemTemplateServiceStub }, + { provide: ActivatedRoute, useValue: { parent: { data: observableOf({ dso: createSuccessfulRemoteDataObject(collection) }) } } }, + { provide: NotificationsService, useValue: notificationsService }, + { provide: ObjectCacheService, useValue: objectCache }, + { provide: RequestService, useValue: requestService } ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); @@ -31,12 +72,51 @@ describe('CollectionMetadataComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(CollectionMetadataComponent); comp = fixture.componentInstance; + router = (comp as any).router; + itemTemplateService = (comp as any).itemTemplateService; fixture.detectChanges(); }); describe('frontendURL', () => { it('should have the right frontendURL set', () => { expect((comp as any).frontendURL).toEqual('/collections/'); - }) + }); + }); + + describe('addItemTemplate', () => { + it('should navigate to the collection\'s itemtemplate page', () => { + spyOn(router, 'navigate'); + comp.addItemTemplate(); + expect(router.navigate).toHaveBeenCalledWith(['collections', collection.uuid, 'itemtemplate']); + }); + }); + + describe('deleteItemTemplate', () => { + describe('when delete returns a success', () => { + beforeEach(() => { + spyOn(itemTemplateService, 'deleteByCollectionID').and.returnValue(observableOf(true)); + comp.deleteItemTemplate(); + }); + + it('should display a success notification', () => { + expect(notificationsService.success).toHaveBeenCalled(); + }); + + it('should reset related object and request cache', () => { + expect(objectCache.remove).toHaveBeenCalledWith(template.self); + expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(collection.self); + }); + }); + + describe('when delete returns a failure', () => { + beforeEach(() => { + spyOn(itemTemplateService, 'deleteByCollectionID').and.returnValue(observableOf(false)); + comp.deleteItemTemplate(); + }); + + it('should display an error notification', () => { + expect(notificationsService.error).toHaveBeenCalled(); + }); + }); }); }); diff --git a/src/app/+collection-page/edit-collection-page/collection-metadata/collection-metadata.component.ts b/src/app/+collection-page/edit-collection-page/collection-metadata/collection-metadata.component.ts index af2ab7d0a7..ca2a38aa86 100644 --- a/src/app/+collection-page/edit-collection-page/collection-metadata/collection-metadata.component.ts +++ b/src/app/+collection-page/edit-collection-page/collection-metadata/collection-metadata.component.ts @@ -3,8 +3,17 @@ import { ComcolMetadataComponent } from '../../../shared/comcol-forms/edit-comco import { Collection } from '../../../core/shared/collection.model'; import { CollectionDataService } from '../../../core/data/collection-data.service'; import { ActivatedRoute, Router } from '@angular/router'; +import { ItemTemplateDataService } from '../../../core/data/item-template-data.service'; +import { Observable } from 'rxjs/internal/Observable'; +import { RemoteData } from '../../../core/data/remote-data'; +import { Item } from '../../../core/shared/item.model'; +import { getRemoteDataPayload, getSucceededRemoteData } from '../../../core/shared/operators'; +import { switchMap, take } from 'rxjs/operators'; +import { combineLatest as combineLatestObservable } from 'rxjs'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; +import { ObjectCacheService } from '../../../core/cache/object-cache.service'; +import { RequestService } from '../../../core/data/request.service'; /** * Component for editing a collection's metadata @@ -17,13 +26,91 @@ export class CollectionMetadataComponent extends ComcolMetadataComponent>; + public constructor( protected collectionDataService: CollectionDataService, + protected itemTemplateService: ItemTemplateDataService, protected router: Router, protected route: ActivatedRoute, protected notificationsService: NotificationsService, - protected translate: TranslateService + protected translate: TranslateService, + protected objectCache: ObjectCacheService, + protected requestService: RequestService ) { super(collectionDataService, router, route, notificationsService, translate); } + + ngOnInit(): void { + super.ngOnInit(); + this.initTemplateItem(); + } + + /** + * Initialize the collection's item template + */ + initTemplateItem() { + this.itemTemplateRD$ = this.dsoRD$.pipe( + getSucceededRemoteData(), + getRemoteDataPayload(), + switchMap((collection: Collection) => this.itemTemplateService.findByCollectionID(collection.uuid)) + ); + } + + /** + * Add a new item template to the collection and redirect to the item template edit page + */ + addItemTemplate() { + const collection$ = this.dsoRD$.pipe( + getSucceededRemoteData(), + getRemoteDataPayload(), + take(1) + ); + const template$ = collection$.pipe( + switchMap((collection: Collection) => this.itemTemplateService.create(new Item(), collection.uuid)), + getSucceededRemoteData(), + getRemoteDataPayload(), + take(1) + ); + + combineLatestObservable(collection$, template$).subscribe(([collection, template]) => { + this.router.navigate(['collections', collection.uuid, 'itemtemplate']); + }); + } + + /** + * Delete the item template from the collection + */ + deleteItemTemplate() { + const collection$ = this.dsoRD$.pipe( + getSucceededRemoteData(), + getRemoteDataPayload(), + take(1) + ); + const template$ = collection$.pipe( + switchMap((collection: Collection) => this.itemTemplateService.findByCollectionID(collection.uuid)), + getSucceededRemoteData(), + getRemoteDataPayload(), + take(1) + ); + + combineLatestObservable(collection$, template$).pipe( + switchMap(([collection, template]) => { + const success$ = this.itemTemplateService.deleteByCollectionID(template, collection.uuid); + this.objectCache.remove(template.self); + this.requestService.removeByHrefSubstring(collection.self); + return success$; + }) + ).subscribe((success: boolean) => { + if (success) { + this.notificationsService.success(null, this.translate.get('collection.edit.template.notifications.delete.success')); + } else { + this.notificationsService.error(null, this.translate.get('collection.edit.template.notifications.delete.error')); + } + this.initTemplateItem(); + }); + } } diff --git a/src/app/+collection-page/edit-item-template-page/edit-item-template-page.component.html b/src/app/+collection-page/edit-item-template-page/edit-item-template-page.component.html new file mode 100644 index 0000000000..5b0f83fa22 --- /dev/null +++ b/src/app/+collection-page/edit-item-template-page/edit-item-template-page.component.html @@ -0,0 +1,9 @@ +
+
+
+

{{ 'collection.edit.template.head' | translate:{ collection: collection?.name } }}

+ + +
+
+
diff --git a/src/app/+collection-page/edit-item-template-page/edit-item-template-page.component.spec.ts b/src/app/+collection-page/edit-item-template-page/edit-item-template-page.component.spec.ts new file mode 100644 index 0000000000..6d5ffc8768 --- /dev/null +++ b/src/app/+collection-page/edit-item-template-page/edit-item-template-page.component.spec.ts @@ -0,0 +1,51 @@ +import { EditItemTemplatePageComponent } from './edit-item-template-page.component'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { SharedModule } from '../../shared/shared.module'; +import { RouterTestingModule } from '@angular/router/testing'; +import { CommonModule } from '@angular/common'; +import { ItemTemplateDataService } from '../../core/data/item-template-data.service'; +import { ActivatedRoute } from '@angular/router'; +import { of as observableOf } from 'rxjs/internal/observable/of'; +import { Collection } from '../../core/shared/collection.model'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { getCollectionEditPath } from '../collection-page-routing.module'; +import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils'; + +describe('EditItemTemplatePageComponent', () => { + let comp: EditItemTemplatePageComponent; + let fixture: ComponentFixture; + let itemTemplateService: ItemTemplateDataService; + let collection: Collection; + + beforeEach(async(() => { + collection = Object.assign(new Collection(), { + uuid: 'collection-id', + id: 'collection-id', + name: 'Fake Collection' + }); + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], + declarations: [EditItemTemplatePageComponent], + providers: [ + { provide: ItemTemplateDataService, useValue: {} }, + { provide: ActivatedRoute, useValue: { parent: { data: observableOf({ dso: createSuccessfulRemoteDataObject(collection) }) } } } + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(EditItemTemplatePageComponent); + comp = fixture.componentInstance; + itemTemplateService = (comp as any).itemTemplateService; + fixture.detectChanges(); + }); + + describe('getCollectionEditUrl', () => { + it('should return the collection\'s edit url', () => { + const url = comp.getCollectionEditUrl(collection); + expect(url).toEqual(getCollectionEditPath(collection.uuid)); + }); + }); +}); diff --git a/src/app/+collection-page/edit-item-template-page/edit-item-template-page.component.ts b/src/app/+collection-page/edit-item-template-page/edit-item-template-page.component.ts new file mode 100644 index 0000000000..329c72d683 --- /dev/null +++ b/src/app/+collection-page/edit-item-template-page/edit-item-template-page.component.ts @@ -0,0 +1,44 @@ +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs/internal/Observable'; +import { RemoteData } from '../../core/data/remote-data'; +import { Collection } from '../../core/shared/collection.model'; +import { ActivatedRoute } from '@angular/router'; +import { first, map } from 'rxjs/operators'; +import { ItemTemplateDataService } from '../../core/data/item-template-data.service'; +import { getCollectionEditPath } from '../collection-page-routing.module'; + +@Component({ + selector: 'ds-edit-item-template-page', + templateUrl: './edit-item-template-page.component.html', +}) +/** + * Component for editing the item template of a collection + */ +export class EditItemTemplatePageComponent implements OnInit { + + /** + * The collection to edit the item template for + */ + collectionRD$: Observable>; + + constructor(protected route: ActivatedRoute, + public itemTemplateService: ItemTemplateDataService) { + } + + ngOnInit(): void { + this.collectionRD$ = this.route.parent.data.pipe(first(), map((data) => data.dso)); + } + + /** + * Get the URL to the collection's edit page + * @param collection + */ + getCollectionEditUrl(collection: Collection): string { + if (collection) { + return getCollectionEditPath(collection.uuid); + } else { + return ''; + } + } + +} diff --git a/src/app/+collection-page/edit-item-template-page/item-template-page.resolver.spec.ts b/src/app/+collection-page/edit-item-template-page/item-template-page.resolver.spec.ts new file mode 100644 index 0000000000..885adadb3f --- /dev/null +++ b/src/app/+collection-page/edit-item-template-page/item-template-page.resolver.spec.ts @@ -0,0 +1,28 @@ +import { of as observableOf } from 'rxjs/internal/observable/of'; +import { first } from 'rxjs/operators'; +import { ItemTemplatePageResolver } from './item-template-page.resolver'; + +describe('ItemTemplatePageResolver', () => { + describe('resolve', () => { + let resolver: ItemTemplatePageResolver; + let itemTemplateService: any; + const uuid = '1234-65487-12354-1235'; + + beforeEach(() => { + itemTemplateService = { + findByCollectionID: (id: string) => observableOf({ payload: { id }, hasSucceeded: true }) + }; + resolver = new ItemTemplatePageResolver(itemTemplateService); + }); + + it('should resolve an item template with the correct id', () => { + resolver.resolve({ params: { id: uuid } } as any, undefined) + .pipe(first()) + .subscribe( + (resolved) => { + expect(resolved.payload.id).toEqual(uuid); + } + ); + }); + }); +}); diff --git a/src/app/+collection-page/edit-item-template-page/item-template-page.resolver.ts b/src/app/+collection-page/edit-item-template-page/item-template-page.resolver.ts new file mode 100644 index 0000000000..919e131836 --- /dev/null +++ b/src/app/+collection-page/edit-item-template-page/item-template-page.resolver.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; +import { RemoteData } from '../../core/data/remote-data'; +import { Item } from '../../core/shared/item.model'; +import { ItemTemplateDataService } from '../../core/data/item-template-data.service'; +import { Observable } from 'rxjs/internal/Observable'; +import { find } from 'rxjs/operators'; +import { hasValue } from '../../shared/empty.util'; +import { followLink } from '../../shared/utils/follow-link-config.model'; + +/** + * This class represents a resolver that requests a specific collection's item template before the route is activated + */ +@Injectable() +export class ItemTemplatePageResolver implements Resolve> { + constructor(private itemTemplateService: ItemTemplateDataService) { + } + + /** + * Method for resolving a collection's item template based on the parameters in the current route + * @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot + * @param {RouterStateSnapshot} state The current RouterStateSnapshot + * @returns Observable<> Emits the found item template based on the parameters in the current route, + * or an error if something went wrong + */ + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable> { + return this.itemTemplateService.findByCollectionID(route.params.id, followLink('templateItemOf')).pipe( + find((RD) => hasValue(RD.error) || RD.hasSucceeded), + ); + } +} diff --git a/src/app/+item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts b/src/app/+item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts index 503759eeff..6f97ec3057 100644 --- a/src/app/+item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts +++ b/src/app/+item-page/edit-item-page/abstract-item-update/abstract-item-update.component.ts @@ -11,6 +11,7 @@ import { first, map } from 'rxjs/operators'; import { RemoteData } from '../../../core/data/remote-data'; import { AbstractTrackableComponent } from '../../../shared/trackable/abstract-trackable.component'; import { environment } from '../../../../environments/environment'; +import { combineLatest as observableCombineLatest } from 'rxjs'; @Component({ selector: 'ds-abstract-item-update', @@ -45,11 +46,12 @@ export class AbstractItemUpdateComponent extends AbstractTrackableComponent impl * Initialize common properties between item-update components */ ngOnInit(): void { - this.route.parent.data.pipe(map((data) => data.item)) - .pipe( - first(), - map((data: RemoteData) => data.payload) - ).subscribe((item: Item) => { + observableCombineLatest(this.route.data, this.route.parent.data).pipe( + map(([data, parentData]) => Object.assign({}, data, parentData)), + map((data) => data.item), + first(), + map((data: RemoteData) => data.payload) + ).subscribe((item: Item) => { this.item = item; this.postItemInit(); }); diff --git a/src/app/+item-page/edit-item-page/edit-item-page.module.ts b/src/app/+item-page/edit-item-page/edit-item-page.module.ts index 44cb4099f0..c3bd9eb924 100644 --- a/src/app/+item-page/edit-item-page/edit-item-page.module.ts +++ b/src/app/+item-page/edit-item-page/edit-item-page.module.ts @@ -78,6 +78,9 @@ import { ObjectValuesPipe } from '../../shared/utils/object-values-pipe'; providers: [ BundleDataService, ObjectValuesPipe + ], + exports: [ + ItemMetadataComponent ] }) export class EditItemPageModule { diff --git a/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.spec.ts b/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.spec.ts index 125fc1fb0c..0bdb4be6cb 100644 --- a/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.spec.ts @@ -142,6 +142,7 @@ describe('ItemBitstreamsComponent', () => { parent: { data: observableOf({ item: createMockRD(item) }) }, + data: observableOf({}), url: url }); bundleService = jasmine.createSpyObj('bundleService', { diff --git a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html index e8ffc28920..80c78941c8 100644 --- a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html +++ b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html @@ -67,4 +67,4 @@ - \ No newline at end of file + diff --git a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts index 45eecc6e7e..5a905fc7ea 100644 --- a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts +++ b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts @@ -58,7 +58,7 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges { metadataFieldSuggestions: BehaviorSubject = new BehaviorSubject([]); constructor( - private metadataFieldService: RegistryService, + private registryService: RegistryService, private objectUpdatesService: ObjectUpdatesService, ) { } @@ -75,7 +75,7 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges { * Sends a new change update for this field to the object updates service */ update(ngModel?: NgModel) { - this.objectUpdatesService.saveChangeFieldUpdate(this.url, this.metadata); + this.objectUpdatesService.saveChangeFieldUpdate(this.url, cloneDeep(this.metadata)); if (hasValue(ngModel)) { this.checkValidity(ngModel); } @@ -103,7 +103,7 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges { * Sends a new remove update for this field to the object updates service */ remove() { - this.objectUpdatesService.saveRemoveFieldUpdate(this.url, this.metadata); + this.objectUpdatesService.saveRemoveFieldUpdate(this.url, cloneDeep(this.metadata)); } /** @@ -123,11 +123,12 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges { /** * Requests all metadata fields that contain the query string in their key * Then sets all found metadata fields as metadataFieldSuggestions + * Ignores fields from metadata schemas "relation" and "relationship" * @param query The query to look for */ findMetadataFieldSuggestions(query: string): void { if (isNotEmpty(query)) { - this.metadataFieldService.queryMetadataFields(query).pipe( + this.registryService.queryMetadataFields(query).pipe( // getSucceededRemoteData(), take(1), map((data) => data.payload.page) diff --git a/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.html b/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.html index 496429a3ba..366f6fffe2 100644 --- a/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.html +++ b/src/app/+item-page/edit-item-page/item-metadata/item-metadata.component.html @@ -1,5 +1,5 @@