diff --git a/src/app/+collection-page/collection-page-routing-paths.ts b/src/app/+collection-page/collection-page-routing-paths.ts index 4f165bb51c..2eebe31e1c 100644 --- a/src/app/+collection-page/collection-page-routing-paths.ts +++ b/src/app/+collection-page/collection-page-routing-paths.ts @@ -24,6 +24,10 @@ export function getCollectionEditRolesRoute(id) { return new URLCombiner(getCollectionPageRoute(id), COLLECTION_EDIT_PATH, COLLECTION_EDIT_ROLES_PATH).toString(); } +export function getCollectionItemTemplateRoute(id) { + return new URLCombiner(getCollectionPageRoute(id), ITEMTEMPLATE_PATH).toString(); +} + export const COLLECTION_CREATE_PATH = 'create'; export const COLLECTION_EDIT_PATH = 'edit'; export const COLLECTION_EDIT_ROLES_PATH = 'roles'; 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 2c2dbf57dd..06b6246792 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 @@ -15,6 +15,7 @@ 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'; +import { getCollectionItemTemplateRoute } from '../../collection-page-routing-paths'; describe('CollectionMetadataComponent', () => { let comp: CollectionMetadataComponent; @@ -35,11 +36,13 @@ describe('CollectionMetadataComponent', () => { self: { href: 'collection-selflink' } } }); + const collectionTemplateHref = 'rest/api/test/collections/template'; - const itemTemplateServiceStub = Object.assign({ - findByCollectionID: () => createSuccessfulRemoteDataObject$(template), - create: () => createSuccessfulRemoteDataObject$(template), - deleteByCollectionID: () => observableOf(true) + const itemTemplateServiceStub = jasmine.createSpyObj('itemTemplateService', { + findByCollectionID: createSuccessfulRemoteDataObject$(template), + create: createSuccessfulRemoteDataObject$(template), + deleteByCollectionID: observableOf(true), + getCollectionEndpoint: observableOf(collectionTemplateHref), }); const notificationsService = jasmine.createSpyObj('notificationsService', { @@ -50,7 +53,7 @@ describe('CollectionMetadataComponent', () => { remove: {} }); const requestService = jasmine.createSpyObj('requestService', { - removeByHrefSubstring: {} + setStaleByHrefSubstring: {} }); beforeEach(waitForAsync(() => { @@ -87,14 +90,14 @@ describe('CollectionMetadataComponent', () => { it('should navigate to the collection\'s itemtemplate page', () => { spyOn(router, 'navigate'); comp.addItemTemplate(); - expect(router.navigate).toHaveBeenCalledWith(['collections', collection.uuid, 'itemtemplate']); + expect(router.navigate).toHaveBeenCalledWith([getCollectionItemTemplateRoute(collection.uuid)]); }); }); describe('deleteItemTemplate', () => { describe('when delete returns a success', () => { beforeEach(() => { - spyOn(itemTemplateService, 'deleteByCollectionID').and.returnValue(observableOf(true)); + (itemTemplateService.deleteByCollectionID as jasmine.Spy).and.returnValue(observableOf(true)); comp.deleteItemTemplate(); }); @@ -103,14 +106,15 @@ describe('CollectionMetadataComponent', () => { }); it('should reset related object and request cache', () => { - expect(objectCache.remove).toHaveBeenCalledWith(template.self); - expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(collection.self); + expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith(collectionTemplateHref); + expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith(template.self); + expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith(collection.self); }); }); describe('when delete returns a failure', () => { beforeEach(() => { - spyOn(itemTemplateService, 'deleteByCollectionID').and.returnValue(observableOf(false)); + (itemTemplateService.deleteByCollectionID as jasmine.Spy).and.returnValue(observableOf(false)); comp.deleteItemTemplate(); }); 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 e8198f49b7..8cb10775ac 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 @@ -7,12 +7,13 @@ import { ItemTemplateDataService } from '../../../core/data/item-template-data.s import { combineLatest as combineLatestObservable, Observable } from 'rxjs'; import { RemoteData } from '../../../core/data/remote-data'; import { Item } from '../../../core/shared/item.model'; -import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../../../core/shared/operators'; -import { switchMap, take } from 'rxjs/operators'; +import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators'; +import { switchMap, tap } from 'rxjs/operators'; 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'; +import { getCollectionItemTemplateRoute } from '../../collection-page-routing-paths'; /** * Component for editing a collection's metadata @@ -53,8 +54,7 @@ export class CollectionMetadataComponent extends ComcolMetadataComponent this.itemTemplateService.findByCollectionID(collection.uuid)) ); } @@ -64,19 +64,20 @@ export class CollectionMetadataComponent extends ComcolMetadataComponent this.itemTemplateService.create(new Item(), collection.uuid)), - getFirstSucceededRemoteData(), - getRemoteDataPayload(), - take(1) + switchMap((collection: Collection) => this.itemTemplateService.create(new Item(), collection.uuid).pipe( + getFirstSucceededRemoteDataPayload(), + )), + ); + const templateHref$ = collection$.pipe( + switchMap((collection) => this.itemTemplateService.getCollectionEndpoint(collection.id)), ); - combineLatestObservable(collection$, template$).subscribe(([collection, template]) => { - this.router.navigate(['collections', collection.uuid, 'itemtemplate']); + combineLatestObservable(collection$, template$, templateHref$).subscribe(([collection, template, templateHref]) => { + this.requestService.setStaleByHrefSubstring(templateHref); + this.router.navigate([getCollectionItemTemplateRoute(collection.uuid)]); }); } @@ -85,23 +86,30 @@ export class CollectionMetadataComponent extends ComcolMetadataComponent this.itemTemplateService.findByCollectionID(collection.uuid)), - getFirstSucceededRemoteData(), - getRemoteDataPayload(), - take(1) + switchMap((collection: Collection) => this.itemTemplateService.findByCollectionID(collection.uuid).pipe( + getFirstSucceededRemoteDataPayload(), + )), + ); + const templateHref$ = collection$.pipe( + switchMap((collection) => this.itemTemplateService.getCollectionEndpoint(collection.id)), ); - 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$; + combineLatestObservable(collection$, template$, templateHref$).pipe( + switchMap(([collection, template, templateHref]) => { + return this.itemTemplateService.deleteByCollectionID(template, collection.uuid).pipe( + tap((success: boolean) => { + if (success) { + this.objectCache.remove(templateHref); + this.objectCache.remove(template.self); + this.requestService.setStaleByHrefSubstring(template.self); + this.requestService.setStaleByHrefSubstring(templateHref); + this.requestService.setStaleByHrefSubstring(collection.self); + } + }) + ); }) ).subscribe((success: boolean) => { if (success) { 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 index 5b0f83fa22..f8c5c92e96 100644 --- 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 @@ -1,9 +1,13 @@
-
-

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

- - +
+ +

{{ '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 index 81bb7ea867..72b776dd7d 100644 --- 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 @@ -9,7 +9,7 @@ import { ActivatedRoute } from '@angular/router'; import { of as observableOf } from 'rxjs'; import { Collection } from '../../core/shared/collection.model'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils'; +import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; import { getCollectionEditRoute } from '../collection-page-routing-paths'; describe('EditItemTemplatePageComponent', () => { @@ -24,11 +24,14 @@ describe('EditItemTemplatePageComponent', () => { id: 'collection-id', name: 'Fake Collection' }); + itemTemplateService = jasmine.createSpyObj('itemTemplateService', { + findByCollectionID: createSuccessfulRemoteDataObject$({}) + }); TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], declarations: [EditItemTemplatePageComponent], providers: [ - { provide: ItemTemplateDataService, useValue: {} }, + { provide: ItemTemplateDataService, useValue: itemTemplateService }, { provide: ActivatedRoute, useValue: { parent: { data: observableOf({ dso: createSuccessfulRemoteDataObject(collection) }) } } } ], schemas: [NO_ERRORS_SCHEMA] @@ -38,7 +41,6 @@ describe('EditItemTemplatePageComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(EditItemTemplatePageComponent); comp = fixture.componentInstance; - itemTemplateService = (comp as any).itemTemplateService; fixture.detectChanges(); }); 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 index 077623f970..5d29eb7f73 100644 --- 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 @@ -3,9 +3,12 @@ import { Observable } from 'rxjs'; 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 { first, map, switchMap } from 'rxjs/operators'; import { ItemTemplateDataService } from '../../core/data/item-template-data.service'; import { getCollectionEditRoute } from '../collection-page-routing-paths'; +import { Item } from '../../core/shared/item.model'; +import { getFirstSucceededRemoteDataPayload } from '../../core/shared/operators'; +import { AlertType } from '../../shared/alert/aletr-type'; @Component({ selector: 'ds-edit-item-template-page', @@ -21,12 +24,27 @@ export class EditItemTemplatePageComponent implements OnInit { */ collectionRD$: Observable>; + /** + * The template item + */ + itemRD$: Observable>; + + /** + * The AlertType enumeration + * @type {AlertType} + */ + AlertTypeEnum = AlertType; + constructor(protected route: ActivatedRoute, public itemTemplateService: ItemTemplateDataService) { } ngOnInit(): void { this.collectionRD$ = this.route.parent.data.pipe(first(), map((data) => data.dso)); + this.itemRD$ = this.collectionRD$.pipe( + getFirstSucceededRemoteDataPayload(), + switchMap((collection) => this.itemTemplateService.findByCollectionID(collection.id)), + ); } /** 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 c6a8d1827c..17ec0ff133 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 @@ -1,4 +1,4 @@ -import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Component, OnInit, OnDestroy, Input } from '@angular/core'; import { FieldUpdate, FieldUpdates @@ -30,7 +30,7 @@ export class AbstractItemUpdateComponent extends AbstractTrackableComponent impl /** * The item to display the edit page for */ - item: Item; + @Input() item: Item; /** * The current values and updates for all this item's fields * Should be initialized in the initializeUpdates method of the child component @@ -63,22 +63,24 @@ export class AbstractItemUpdateComponent extends AbstractTrackableComponent impl * Initialize common properties between item-update components */ ngOnInit(): void { - this.itemUpdateSubscription = observableCombineLatest([this.route.data, this.route.parent.data]).pipe( - map(([data, parentData]: [Data, Data]) => Object.assign({}, data, parentData)), - map((data: any) => data.dso), - tap((rd: RemoteData) => { - this.item = rd.payload; - }), - switchMap((rd: RemoteData) => { - return this.itemService.findByHref(rd.payload._links.self.href, true, true, ...ITEM_PAGE_LINKS_TO_FOLLOW); - }), - getAllSucceededRemoteData() - ).subscribe((rd: RemoteData) => { - this.item = rd.payload; - this.itemPageRoute = getItemPageRoute(this.item); - this.postItemInit(); - this.initializeUpdates(); - }); + if (hasValue(this.item)) { + this.setItem(this.item); + } else { + // The item wasn't provided through an input, retrieve it from the route instead. + this.itemUpdateSubscription = observableCombineLatest([this.route.data, this.route.parent.data]).pipe( + map(([data, parentData]: [Data, Data]) => Object.assign({}, data, parentData)), + map((data: any) => data.dso), + tap((rd: RemoteData) => { + this.item = rd.payload; + }), + switchMap((rd: RemoteData) => { + return this.itemService.findByHref(rd.payload._links.self.href, true, true, ...ITEM_PAGE_LINKS_TO_FOLLOW); + }), + getAllSucceededRemoteData() + ).subscribe((rd: RemoteData) => { + this.setItem(rd.payload); + }); + } this.discardTimeOut = environment.item.edit.undoTimeout; this.url = this.router.url; @@ -97,6 +99,13 @@ export class AbstractItemUpdateComponent extends AbstractTrackableComponent impl this.initializeUpdates(); } + setItem(item: Item) { + this.item = item; + this.itemPageRoute = getItemPageRoute(this.item); + this.postItemInit(); + this.initializeUpdates(); + } + ngOnDestroy() { if (hasValue(this.itemUpdateSubscription)) { this.itemUpdateSubscription.unsubscribe(); diff --git a/src/app/core/data/item-template-data.service.ts b/src/app/core/data/item-template-data.service.ts index 64f22d8aa5..19e6941385 100644 --- a/src/app/core/data/item-template-data.service.ts +++ b/src/app/core/data/item-template-data.service.ts @@ -22,6 +22,7 @@ import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; import { NoContent } from '../shared/NoContent.model'; import { hasValue } from '../../shared/empty.util'; import { Operation } from 'fast-json-patch'; +import { getFirstCompletedRemoteData } from '../shared/operators'; /* tslint:disable:max-classes-per-file */ /** @@ -57,15 +58,23 @@ class DataServiceImpl extends ItemDataService { super(requestService, rdbService, store, bs, objectCache, halService, notificationsService, http, comparator, bundleService); } + /** + * Get the endpoint based on a collection + * @param collectionID The ID of the collection to base the endpoint on + */ + public getCollectionEndpoint(collectionID: string): Observable { + return this.collectionService.getIDHrefObs(collectionID).pipe( + switchMap((href: string) => this.halService.getEndpoint(this.collectionLinkPath, href)) + ); + } + /** * Set the endpoint to be based on a collection * @param collectionID The ID of the collection to base the endpoint on */ private setCollectionEndpoint(collectionID: string) { this.collectionEndpoint = true; - this.endpoint$ = this.collectionService.getIDHrefObs(collectionID).pipe( - switchMap((href: string) => this.halService.getEndpoint(this.collectionLinkPath, href)) - ); + this.endpoint$ = this.getCollectionEndpoint(collectionID); } /** @@ -130,6 +139,7 @@ class DataServiceImpl extends ItemDataService { deleteByCollectionID(item: Item, collectionID: string): Observable { this.setRegularEndpoint(); return super.delete(item.uuid).pipe( + getFirstCompletedRemoteData(), map((response: RemoteData) => hasValue(response) && response.hasSucceeded) ); } @@ -209,5 +219,13 @@ export class ItemTemplateDataService implements UpdateDataService { deleteByCollectionID(item: Item, collectionID: string): Observable { return this.dataService.deleteByCollectionID(item, collectionID); } + + /** + * Get the endpoint based on a collection + * @param collectionID The ID of the collection to base the endpoint on + */ + getCollectionEndpoint(collectionID: string): Observable { + return this.dataService.getCollectionEndpoint(collectionID); + } } /* tslint:enable:max-classes-per-file */ diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index f60abfb253..fa9e220f7c 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -777,10 +777,14 @@ "collection.edit.template.edit-button": "Edit", + "collection.edit.template.error": "An error occurred retrieving the template item", + "collection.edit.template.head": "Edit Template Item for Collection \"{{ collection }}\"", "collection.edit.template.label": "Template item", + "collection.edit.template.loading": "Loading template item...", + "collection.edit.template.notifications.delete.error": "Failed to delete the item template", "collection.edit.template.notifications.delete.success": "Successfully deleted the item template",