diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 5e8450eb80..bc466c54b0 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -29,11 +29,22 @@ } }, "edit": { - "head": "Edit Collection" + "head": "Edit Collection", + "delete": "Delete this collection" }, "create": { "head": "Create a Collection", "sub-head": "Create a Collection for Community {{ parent }}" + }, + "delete": { + "head": "Delete Collection", + "text": "Are you sure you want to delete collection \"{{ dso }}\"", + "confirm": "Confirm", + "cancel": "Cancel", + "notification": { + "success": "Successfully deleted collection", + "fail": "Collection could not be deleted" + } } }, "community": { @@ -60,11 +71,22 @@ } }, "edit": { - "head": "Edit Community" + "head": "Edit Community", + "delete": "Delete this community" }, "create": { "head": "Create a Community", "sub-head": "Create a Sub-Community for Community {{ parent }}" + }, + "delete": { + "head": "Delete Community", + "text": "Are you sure you want to delete community \"{{ dso }}\"", + "confirm": "Confirm", + "cancel": "Cancel", + "notification": { + "success": "Successfully deleted community", + "fail": "Community could not be deleted" + } } }, "item": { diff --git a/src/app/+collection-page/collection-page-routing.module.ts b/src/app/+collection-page/collection-page-routing.module.ts index ec53796e61..ddcf36a0cc 100644 --- a/src/app/+collection-page/collection-page-routing.module.ts +++ b/src/app/+collection-page/collection-page-routing.module.ts @@ -7,6 +7,7 @@ import { CreateCollectionPageComponent } from './create-collection-page/create-c import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { EditCollectionPageComponent } from './edit-collection-page/edit-collection-page.component'; import { CreateCollectionPageGuard } from './create-collection-page/create-collection-page.guard'; +import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component'; @NgModule({ imports: [ @@ -25,6 +26,15 @@ import { CreateCollectionPageGuard } from './create-collection-page/create-colle dso: CollectionPageResolver } }, + { + path: ':id/delete', + pathMatch: 'full', + component: DeleteCollectionPageComponent, + canActivate: [AuthenticatedGuard], + resolve: { + dso: CollectionPageResolver + } + }, { path: ':id', component: CollectionPageComponent, diff --git a/src/app/+collection-page/collection-page.module.ts b/src/app/+collection-page/collection-page.module.ts index 63a03f4299..8424cc02a4 100644 --- a/src/app/+collection-page/collection-page.module.ts +++ b/src/app/+collection-page/collection-page.module.ts @@ -9,6 +9,7 @@ import { CreateCollectionPageComponent } from './create-collection-page/create-c import { CollectionFormComponent } from './collection-form/collection-form.component'; import { SearchPageModule } from '../+search-page/search-page.module'; import { EditCollectionPageComponent } from './edit-collection-page/edit-collection-page.component'; +import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component'; @NgModule({ imports: [ @@ -21,6 +22,7 @@ import { EditCollectionPageComponent } from './edit-collection-page/edit-collect CollectionPageComponent, CreateCollectionPageComponent, EditCollectionPageComponent, + DeleteCollectionPageComponent, CollectionFormComponent ] }) diff --git a/src/app/+collection-page/delete-collection-page/delete-collection-page.component.html b/src/app/+collection-page/delete-collection-page/delete-collection-page.component.html new file mode 100644 index 0000000000..cfd09f2bbd --- /dev/null +++ b/src/app/+collection-page/delete-collection-page/delete-collection-page.component.html @@ -0,0 +1,19 @@ +
+
+ +
+ +

{{ 'community.delete.text' | translate:{ dso: dso.name } }}

+ + +
+
+ +
+ +
diff --git a/src/app/+collection-page/delete-collection-page/delete-collection-page.component.scss b/src/app/+collection-page/delete-collection-page/delete-collection-page.component.scss new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/app/+collection-page/delete-collection-page/delete-collection-page.component.scss @@ -0,0 +1 @@ + diff --git a/src/app/+collection-page/delete-collection-page/delete-collection-page.component.spec.ts b/src/app/+collection-page/delete-collection-page/delete-collection-page.component.spec.ts new file mode 100644 index 0000000000..d64c1d1915 --- /dev/null +++ b/src/app/+collection-page/delete-collection-page/delete-collection-page.component.spec.ts @@ -0,0 +1,41 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute, Router } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { CommonModule } from '@angular/common'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { SharedModule } from '../../shared/shared.module'; +import { of as observableOf } from 'rxjs'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { DeleteCollectionPageComponent } from './delete-collection-page.component'; +import { CollectionDataService } from '../../core/data/collection-data.service'; + +describe('DeleteCollectionPageComponent', () => { + let comp: DeleteCollectionPageComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], + declarations: [DeleteCollectionPageComponent], + providers: [ + { provide: CollectionDataService, useValue: {} }, + { provide: ActivatedRoute, useValue: { data: observableOf({ dso: { payload: {} } }) } }, + { provide: NotificationsService, useValue: {} }, + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DeleteCollectionPageComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + }); + + describe('frontendURL', () => { + it('should have the right frontendURL set', () => { + expect((comp as any).frontendURL).toEqual('/collections/'); + }) + }); +}); diff --git a/src/app/+collection-page/delete-collection-page/delete-collection-page.component.ts b/src/app/+collection-page/delete-collection-page/delete-collection-page.component.ts new file mode 100644 index 0000000000..80abb83694 --- /dev/null +++ b/src/app/+collection-page/delete-collection-page/delete-collection-page.component.ts @@ -0,0 +1,33 @@ +import { Component } from '@angular/core'; +import { Community } from '../../core/shared/community.model'; +import { CommunityDataService } from '../../core/data/community-data.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { NormalizedCommunity } from '../../core/cache/models/normalized-community.model'; +import { DeleteComColPageComponent } from '../../shared/comcol-forms/delete-comcol-page/delete-comcol-page.component'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { CollectionDataService } from '../../core/data/collection-data.service'; +import { NormalizedCollection } from '../../core/cache/models/normalized-collection.model'; +import { Collection } from '../../core/shared/collection.model'; +import { TranslateService } from '@ngx-translate/core'; + +/** + * Component that represents the page where a user can delete an existing Collection + */ +@Component({ + selector: 'ds-delete-collection', + styleUrls: ['./delete-collection-page.component.scss'], + templateUrl: './delete-collection-page.component.html' +}) +export class DeleteCollectionPageComponent extends DeleteComColPageComponent { + protected frontendURL = '/collections/'; + + public constructor( + protected dsoDataService: CollectionDataService, + protected router: Router, + protected route: ActivatedRoute, + protected notifications: NotificationsService, + protected translate: TranslateService + ) { + super(dsoDataService, router, route, notifications, translate); + } +} diff --git a/src/app/+collection-page/edit-collection-page/edit-collection-page.component.html b/src/app/+collection-page/edit-collection-page/edit-collection-page.component.html index 1308af919f..c389c681ce 100644 --- a/src/app/+collection-page/edit-collection-page/edit-collection-page.component.html +++ b/src/app/+collection-page/edit-collection-page/edit-collection-page.component.html @@ -1,8 +1,11 @@
- + + + {{'collection.edit.delete' + | translate}}
-
diff --git a/src/app/+collection-page/edit-collection-page/edit-collection-page.component.spec.ts b/src/app/+collection-page/edit-collection-page/edit-collection-page.component.spec.ts index 6b01f13574..193cb293e4 100644 --- a/src/app/+collection-page/edit-collection-page/edit-collection-page.component.spec.ts +++ b/src/app/+collection-page/edit-collection-page/edit-collection-page.component.spec.ts @@ -1,11 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; import { CommonModule } from '@angular/common'; import { RouterTestingModule } from '@angular/router/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { EditCollectionPageComponent } from './edit-collection-page.component'; -import { RouteService } from '../../shared/services/route.service'; import { SharedModule } from '../../shared/shared.module'; import { CollectionDataService } from '../../core/data/collection-data.service'; import { of as observableOf } from 'rxjs'; @@ -20,9 +19,7 @@ describe('EditCollectionPageComponent', () => { declarations: [EditCollectionPageComponent], providers: [ { provide: CollectionDataService, useValue: {} }, - { provide: RouteService, useValue: {} }, - { provide: Router, useValue: {} }, - { provide: ActivatedRoute, useValue: { data: observableOf({dso: undefined}) } }, + { provide: ActivatedRoute, useValue: { data: observableOf({ dso: { payload: {} } }) } }, ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); diff --git a/src/app/+collection-page/edit-collection-page/edit-collection-page.component.ts b/src/app/+collection-page/edit-collection-page/edit-collection-page.component.ts index 2ffb4925a0..9bbdbfb9a1 100644 --- a/src/app/+collection-page/edit-collection-page/edit-collection-page.component.ts +++ b/src/app/+collection-page/edit-collection-page/edit-collection-page.component.ts @@ -1,5 +1,4 @@ import { Component } from '@angular/core'; -import { RouteService } from '../../shared/services/route.service'; import { ActivatedRoute, Router } from '@angular/router'; import { EditComColPageComponent } from '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component'; import { NormalizedCollection } from '../../core/cache/models/normalized-collection.model'; @@ -19,10 +18,9 @@ export class EditCollectionPageComponent extends EditComColPageComponent +
+ +
+ +

{{ 'community.delete.text' | translate:{ dso: dso.name } }}

+ + +
+
+ +
+ + diff --git a/src/app/+community-page/delete-community-page/delete-community-page.component.scss b/src/app/+community-page/delete-community-page/delete-community-page.component.scss new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/app/+community-page/delete-community-page/delete-community-page.component.scss @@ -0,0 +1 @@ + diff --git a/src/app/+community-page/delete-community-page/delete-community-page.component.spec.ts b/src/app/+community-page/delete-community-page/delete-community-page.component.spec.ts new file mode 100644 index 0000000000..f18c4fb1f1 --- /dev/null +++ b/src/app/+community-page/delete-community-page/delete-community-page.component.spec.ts @@ -0,0 +1,42 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute, Router } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { CommonModule } from '@angular/common'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { RouteService } from '../../shared/services/route.service'; +import { SharedModule } from '../../shared/shared.module'; +import { of as observableOf } from 'rxjs'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { DeleteCommunityPageComponent } from './delete-community-page.component'; +import { CommunityDataService } from '../../core/data/community-data.service'; + +describe('DeleteCommunityPageComponent', () => { + let comp: DeleteCommunityPageComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], + declarations: [DeleteCommunityPageComponent], + providers: [ + { provide: CommunityDataService, useValue: {} }, + { provide: ActivatedRoute, useValue: { data: observableOf({ dso: { payload: {} } }) } }, + { provide: NotificationsService, useValue: {} }, + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DeleteCommunityPageComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + }); + + describe('frontendURL', () => { + it('should have the right frontendURL set', () => { + expect((comp as any).frontendURL).toEqual('/communities/'); + }) + }); +}); diff --git a/src/app/+community-page/delete-community-page/delete-community-page.component.ts b/src/app/+community-page/delete-community-page/delete-community-page.component.ts new file mode 100644 index 0000000000..01741a7577 --- /dev/null +++ b/src/app/+community-page/delete-community-page/delete-community-page.component.ts @@ -0,0 +1,30 @@ +import { Component } from '@angular/core'; +import { Community } from '../../core/shared/community.model'; +import { CommunityDataService } from '../../core/data/community-data.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { NormalizedCommunity } from '../../core/cache/models/normalized-community.model'; +import { DeleteComColPageComponent } from '../../shared/comcol-forms/delete-comcol-page/delete-comcol-page.component'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; + +/** + * Component that represents the page where a user can delete an existing Community + */ +@Component({ + selector: 'ds-delete-community', + styleUrls: ['./delete-community-page.component.scss'], + templateUrl: './delete-community-page.component.html' +}) +export class DeleteCommunityPageComponent extends DeleteComColPageComponent { + protected frontendURL = '/communities/'; + + public constructor( + protected dsoDataService: CommunityDataService, + protected router: Router, + protected route: ActivatedRoute, + protected notifications: NotificationsService, + protected translate: TranslateService + ) { + super(dsoDataService, router, route, notifications, translate); + } +} diff --git a/src/app/+community-page/edit-community-page/edit-community-page.component.html b/src/app/+community-page/edit-community-page/edit-community-page.component.html index 98649243cc..cedb771c14 100644 --- a/src/app/+community-page/edit-community-page/edit-community-page.component.html +++ b/src/app/+community-page/edit-community-page/edit-community-page.component.html @@ -1,8 +1,12 @@
-
-
- +
+
+ + + {{'community.edit.delete' + | translate}} +
-
-
diff --git a/src/app/+community-page/edit-community-page/edit-community-page.component.spec.ts b/src/app/+community-page/edit-community-page/edit-community-page.component.spec.ts index 764cffe8b5..54f2133ce7 100644 --- a/src/app/+community-page/edit-community-page/edit-community-page.component.spec.ts +++ b/src/app/+community-page/edit-community-page/edit-community-page.component.spec.ts @@ -1,10 +1,9 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; import { CommonModule } from '@angular/common'; import { RouterTestingModule } from '@angular/router/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { RouteService } from '../../shared/services/route.service'; import { SharedModule } from '../../shared/shared.module'; import { of as observableOf } from 'rxjs'; import { EditCommunityPageComponent } from './edit-community-page.component'; @@ -20,9 +19,7 @@ describe('EditCommunityPageComponent', () => { declarations: [EditCommunityPageComponent], providers: [ { provide: CommunityDataService, useValue: {} }, - { provide: RouteService, useValue: {} }, - { provide: Router, useValue: {} }, - { provide: ActivatedRoute, useValue: { data: observableOf({dso: undefined}) } }, + { provide: ActivatedRoute, useValue: { data: observableOf({ dso: { payload: {} } }) } }, ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); diff --git a/src/app/+community-page/edit-community-page/edit-community-page.component.ts b/src/app/+community-page/edit-community-page/edit-community-page.component.ts index 8db5eab204..68f092e915 100644 --- a/src/app/+community-page/edit-community-page/edit-community-page.component.ts +++ b/src/app/+community-page/edit-community-page/edit-community-page.component.ts @@ -1,7 +1,6 @@ import { Component } from '@angular/core'; import { Community } from '../../core/shared/community.model'; import { CommunityDataService } from '../../core/data/community-data.service'; -import { RouteService } from '../../shared/services/route.service'; import { ActivatedRoute, Router } from '@angular/router'; import { NormalizedCommunity } from '../../core/cache/models/normalized-community.model'; import { EditComColPageComponent } from '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component'; @@ -19,10 +18,9 @@ export class EditCommunityPageComponent extends EditComColPageComponent { beforeEach(async(() => { @@ -46,14 +44,12 @@ describe('ItemDeleteComponent', () => { }); mockItemDataService = jasmine.createSpyObj('mockItemDataService', { - delete: observableOf(new RestResponse(true, '200')) + delete: observableOf(true) }); routeStub = { data: observableOf({ - item: new RemoteData(false, false, true, null, { - id: 'fake-id' - }) + item: new RemoteData(false, false, true, null, mockItem) }) }; @@ -63,10 +59,10 @@ describe('ItemDeleteComponent', () => { imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], declarations: [ItemDeleteComponent], providers: [ - {provide: ActivatedRoute, useValue: routeStub}, - {provide: Router, useValue: routerStub}, - {provide: ItemDataService, useValue: mockItemDataService}, - {provide: NotificationsService, useValue: notificationsServiceStub}, + { provide: ActivatedRoute, useValue: routeStub }, + { provide: Router, useValue: routerStub }, + { provide: ItemDataService, useValue: mockItemDataService }, + { provide: NotificationsService, useValue: notificationsServiceStub }, ], schemas: [ CUSTOM_ELEMENTS_SCHEMA ] @@ -74,9 +70,6 @@ describe('ItemDeleteComponent', () => { })); beforeEach(() => { - successfulRestResponse = new RestResponse(true, '200'); - failRestResponse = new RestResponse(false, '500'); - fixture = TestBed.createComponent(ItemDeleteComponent); comp = fixture.componentInstance; fixture.detectChanges(); @@ -95,22 +88,21 @@ describe('ItemDeleteComponent', () => { describe('performAction', () => { it('should call delete function from the ItemDataService', () => { - spyOn(comp, 'processRestResponse'); + spyOn(comp, 'notify'); comp.performAction(); - - expect(mockItemDataService.delete).toHaveBeenCalledWith(mockItem.id); - expect(comp.processRestResponse).toHaveBeenCalled(); + expect(mockItemDataService.delete).toHaveBeenCalledWith(mockItem); + expect(comp.notify).toHaveBeenCalled(); }); }); - describe('processRestResponse', () => { + describe('notify', () => { it('should navigate to the homepage on successful deletion of the item', () => { - comp.processRestResponse(successfulRestResponse); + comp.notify(true); expect(routerStub.navigate).toHaveBeenCalledWith(['']); }); }); - describe('processRestResponse', () => { + describe('notify', () => { it('should navigate to the item edit page on failed deletion of the item', () => { - comp.processRestResponse(failRestResponse); + comp.notify(false); expect(routerStub.navigate).toHaveBeenCalledWith([getItemEditPath('fake-id')]); }); }); diff --git a/src/app/+item-page/edit-item-page/item-delete/item-delete.component.ts b/src/app/+item-page/edit-item-page/item-delete/item-delete.component.ts index 95f25c67bc..2700b45475 100644 --- a/src/app/+item-page/edit-item-page/item-delete/item-delete.component.ts +++ b/src/app/+item-page/edit-item-page/item-delete/item-delete.component.ts @@ -19,20 +19,19 @@ export class ItemDeleteComponent extends AbstractSimpleItemActionComponent { * Perform the delete action to the item */ performAction() { - this.itemDataService.delete(this.item.id).pipe(first()).subscribe( - (response: RestResponse) => { - this.processRestResponse(response); + this.itemDataService.delete(this.item).pipe(first()).subscribe( + (succeeded: boolean) => { + this.notify(succeeded); } ); } /** - * Process the RestResponse retrieved from the server. * When the item is successfully delete, navigate to the homepage, otherwise navigate back to the item edit page * @param response */ - processRestResponse(response: RestResponse) { - if (response.isSuccessful) { + notify(succeeded: boolean) { + if (succeeded) { this.notificationsService.success(this.translateService.get('item.edit.' + this.messageKey + '.success')); this.router.navigate(['']); } else { diff --git a/src/app/core/data/comcol-data.service.spec.ts b/src/app/core/data/comcol-data.service.spec.ts index 1b520d83ce..4c20a4cfeb 100644 --- a/src/app/core/data/comcol-data.service.spec.ts +++ b/src/app/core/data/comcol-data.service.spec.ts @@ -86,7 +86,7 @@ describe('ComColDataService', () => { function initMockCommunityDataService(): CommunityDataService { return jasmine.createSpyObj('responseCache', { getEndpoint: hot('--a-', { a: communitiesEndpoint }), - getFindByIDHref: cold('b-', { b: communityEndpoint }) + getIDHref: cold('b-', { b: communityEndpoint }) }); } diff --git a/src/app/core/data/comcol-data.service.ts b/src/app/core/data/comcol-data.service.ts index 0616e9c2ed..8a1ea51bb3 100644 --- a/src/app/core/data/comcol-data.service.ts +++ b/src/app/core/data/comcol-data.service.ts @@ -42,7 +42,7 @@ export abstract class ComColDataService this.cds.getFindByIDHref(endpoint, options.scopeID)), + mergeMap((endpoint: string) => this.cds.getIDHref(endpoint, options.scopeID)), filter((href: string) => isNotEmpty(href)), take(1), tap((href: string) => { diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index c164ca8b56..136879ba7e 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -1,12 +1,12 @@ import { - delay, distinctUntilChanged, filter, find, - switchMap, + first, map, - take, - tap, first, mergeMap + mergeMap, + switchMap, + take } from 'rxjs/operators'; import { Observable } from 'rxjs'; import { Store } from '@ngrx/store'; @@ -19,28 +19,26 @@ import { PaginatedList } from './paginated-list'; import { RemoteData } from './remote-data'; import { CreateRequest, + DeleteByIDRequest, FindAllOptions, FindAllRequest, FindByIDRequest, - GetRequest, RestRequest + GetRequest } from './request.models'; import { RequestService } from './request.service'; import { NormalizedObject } from '../cache/models/normalized-object.model'; -import { compare, Operation } from 'fast-json-patch'; +import { Operation } from 'fast-json-patch'; import { ObjectCacheService } from '../cache/object-cache.service'; import { DSpaceObject } from '../shared/dspace-object.model'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; -import { - configureRequest, - filterSuccessfulResponses, getResourceLinksFromResponse, - getResponseFromEntry -} from '../shared/operators'; -import { DSOSuccessResponse, ErrorResponse, RestResponse } from '../cache/response.models'; +import { configureRequest, getResponseFromEntry } from '../shared/operators'; +import { ErrorResponse, RestResponse } from '../cache/response.models'; import { NotificationOptions } from '../../shared/notifications/models/notification-options.model'; import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; import { NormalizedObjectFactory } from '../cache/models/normalized-object-factory'; import { CacheableObject } from '../cache/object-cache.reducer'; +import { RequestEntry } from './request.reducer'; import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; import { ChangeAnalyzer } from './change-analyzer'; @@ -97,13 +95,18 @@ export abstract class DataService(hrefObs) as Observable>>; } - getFindByIDHref(endpoint, resourceID): string { + /** + * Create the HREF for a specific object based on its identifier + * @param endpoint The base endpoint for the type of object + * @param resourceID The identifier for the object + */ + getIDHref(endpoint, resourceID): string { return `${endpoint}/${resourceID}`; } findById(id: string): Observable> { const hrefObs = this.halService.getEndpoint(this.linkPath).pipe( - map((endpoint: string) => this.getFindByIDHref(endpoint, id))); + map((endpoint: string) => this.getIDHref(endpoint, id))); hrefObs.pipe( find((href: string) => hasValue(href))) @@ -201,4 +204,29 @@ export abstract class DataService { + const requestId = this.requestService.generateRequestId(); + + const hrefObs = this.halService.getEndpoint(this.linkPath).pipe( + map((endpoint: string) => this.getIDHref(endpoint, dso.uuid))); + + hrefObs.pipe( + find((href: string) => hasValue(href)), + map((href: string) => { + const request = new DeleteByIDRequest(requestId, href, dso.uuid); + this.requestService.configure(request); + }) + ).subscribe(); + + return this.requestService.getByUUID(requestId).pipe( + find((request: RequestEntry) => request.completed), + map((request: RequestEntry) => request.response.isSuccessful) + ); + } + } diff --git a/src/app/core/data/dspace-object-data.service.ts b/src/app/core/data/dspace-object-data.service.ts index d485fd0bc4..86ce9be7a9 100644 --- a/src/app/core/data/dspace-object-data.service.ts +++ b/src/app/core/data/dspace-object-data.service.ts @@ -37,7 +37,7 @@ class DataServiceImpl extends DataService return this.halService.getEndpoint(linkPath); } - getFindByIDHref(endpoint, resourceID): string { + getIDHref(endpoint, resourceID): string { return endpoint.replace(/\{\?uuid\}/,`?uuid=${resourceID}`); } } diff --git a/src/app/core/data/item-data.service.spec.ts b/src/app/core/data/item-data.service.spec.ts index 6cf7e503d3..02c70791b5 100644 --- a/src/app/core/data/item-data.service.spec.ts +++ b/src/app/core/data/item-data.service.spec.ts @@ -162,25 +162,4 @@ describe('ItemDataService', () => { }); }); - describe('getItemDeleteEndpoint', () => { - beforeEach(() => { - scheduler = getTestScheduler(); - service = initTestService(); - }); - - it('should return the endpoint to make an item private or public', () => { - const result = service.getItemDeleteEndpoint(scopeID); - const expected = cold('a', {a: ScopedItemEndpoint}); - - expect(result).toBeObservable(expected); - }); - - it('should delete the item', () => { - const expected = new RestResponse(true, '200'); - const result = service.delete(scopeID); - result.subscribe((v) => expect(v).toEqual(expected)); - - }); - }); - }); diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index c67b49f70d..bd3c42a67c 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -14,7 +14,7 @@ import { URLCombiner } from '../url-combiner/url-combiner'; import { DataService } from './data.service'; import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { DeleteRequest, FindAllOptions, PatchRequest, RestRequest } from './request.models'; +import { FindAllOptions, PatchRequest, RestRequest } from './request.models'; import { ObjectCacheService } from '../cache/object-cache.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; @@ -64,7 +64,7 @@ export class ItemDataService extends DataService { */ public getItemWithdrawEndpoint(itemId: string): Observable { return this.halService.getEndpoint(this.linkPath).pipe( - map((endpoint: string) => this.getFindByIDHref(endpoint, itemId)) + map((endpoint: string) => this.getIDHref(endpoint, itemId)) ); } @@ -74,17 +74,7 @@ export class ItemDataService extends DataService { */ public getItemDiscoverableEndpoint(itemId: string): Observable { return this.halService.getEndpoint(this.linkPath).pipe( - map((endpoint: string) => this.getFindByIDHref(endpoint, itemId)) - ); - } - - /** - * Get the endpoint to delete the item - * @param itemId - */ - public getItemDeleteEndpoint(itemId: string): Observable { - return this.halService.getEndpoint(this.linkPath).pipe( - map((endpoint: string) => this.getFindByIDHref(endpoint, itemId)) + map((endpoint: string) => this.getIDHref(endpoint, itemId)) ); } @@ -129,22 +119,4 @@ export class ItemDataService extends DataService { map((requestEntry: RequestEntry) => requestEntry.response) ); } - - /** - * Delete the item - * @param itemId - */ - public delete(itemId: string) { - return this.getItemDeleteEndpoint(itemId).pipe( - distinctUntilChanged(), - map((endpointURL: string) => - new DeleteRequest(this.requestService.generateRequestId(), endpointURL) - ), - configureRequest(this.requestService), - map((request: RestRequest) => request.href), - getRequestFromRequestHref(this.requestService), - map((requestEntry: RequestEntry) => requestEntry.response) - ); - } - } diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts index 6c4c89e492..e4920058ac 100644 --- a/src/app/core/data/request.models.ts +++ b/src/app/core/data/request.models.ts @@ -227,6 +227,19 @@ export class CreateRequest extends PostRequest { } } +/** + * Request to delete an object based on its identifier + */ +export class DeleteByIDRequest extends DeleteRequest { + constructor( + uuid: string, + href: string, + public resourceID: string + ) { + super(uuid, href); + } +} + export class RequestError extends Error { statusText: string; } diff --git a/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.ts b/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.ts index f8bed5b814..fc7ee3ee70 100644 --- a/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.ts +++ b/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.ts @@ -21,7 +21,7 @@ import { NormalizedDSpaceObject } from '../../../core/cache/models/normalized-ds }) export class CreateComColPageComponent implements OnInit { /** - * Frontend endpoint where for this type of DSP + * Frontend endpoint for this type of DSO */ protected frontendURL: string; diff --git a/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.spec.ts b/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.spec.ts new file mode 100644 index 0000000000..81ec9c47a0 --- /dev/null +++ b/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.spec.ts @@ -0,0 +1,155 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { CommunityDataService } from '../../../core/data/community-data.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { of as observableOf } from 'rxjs'; +import { Community } from '../../../core/shared/community.model'; +import { SharedModule } from '../../shared.module'; +import { CommonModule } from '@angular/common'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; +import { NormalizedDSpaceObject } from '../../../core/cache/models/normalized-dspace-object.model'; +import { DataService } from '../../../core/data/data.service'; +import { DeleteComColPageComponent } from './delete-comcol-page.component'; +import { NotificationsService } from '../../notifications/notifications.service'; +import { NotificationsServiceStub } from '../../testing/notifications-service-stub'; + +describe('DeleteComColPageComponent', () => { + let comp: DeleteComColPageComponent; + let fixture: ComponentFixture>; + let dsoDataService: CommunityDataService; + let router: Router; + + let community; + let newCommunity; + let routerStub; + let routeStub; + let notificationsService; + const validUUID = 'valid-uuid'; + const invalidUUID = 'invalid-uuid'; + const frontendURL = '/testType'; + function initializeVars() { + community = Object.assign(new Community(), { + uuid: 'a20da287-e174-466a-9926-f66b9300d347', + metadata: [{ + key: 'dc.title', + value: 'test community' + }] + }); + + newCommunity = Object.assign(new Community(), { + uuid: '1ff59938-a69a-4e62-b9a4-718569c55d48', + metadata: [{ + key: 'dc.title', + value: 'new community' + }] + }); + + dsoDataService = jasmine.createSpyObj( + 'dsoDataService', + { + delete: observableOf(true) + }); + + routerStub = { + navigate: (commands) => commands + }; + + routeStub = { + data: observableOf(community) + }; + + } + + beforeEach(async(() => { + initializeVars(); + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], + providers: [ + { provide: DataService, useValue: dsoDataService }, + { provide: Router, useValue: routerStub }, + { provide: ActivatedRoute, useValue: routeStub }, + { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DeleteComColPageComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + notificationsService = (comp as any).notifications; + (comp as any).frontendURL = frontendURL; + router = (comp as any).router; + }); + + describe('onConfirm', () => { + let data1; + let data2; + beforeEach(() => { + data1 = Object.assign(new Community(), { + uuid: validUUID, + metadata: [{ + key: 'dc.title', + value: 'test' + }] + }); + + data2 = Object.assign(new Community(), { + uuid: invalidUUID, + metadata: [{ + key: 'dc.title', + value: 'test' + }] + }); + }); + + it('should show an error notification on failure', () => { + (dsoDataService.delete as any).and.returnValue(observableOf(false)); + spyOn(notificationsService, 'error'); + spyOn(router, 'navigate'); + comp.onConfirm(data2); + fixture.detectChanges(); + expect(notificationsService.error).toHaveBeenCalled(); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('should show a success notification on success and navigate', () => { + spyOn(notificationsService, 'success'); + spyOn(router, 'navigate'); + comp.onConfirm(data1); + fixture.detectChanges(); + expect(notificationsService.success).toHaveBeenCalled(); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('should call delete on the data service', () => { + comp.onConfirm(data1); + fixture.detectChanges(); + expect(dsoDataService.delete).toHaveBeenCalledWith(data1); + }); + }); + + describe('onCancel', () => { + let data1; + beforeEach(() => { + data1 = Object.assign(new Community(), { + uuid: validUUID, + metadata: [{ + key: 'dc.title', + value: 'test' + }] + }); + }); + + it('should redirect to the edit page', () => { + const redirectURL = frontendURL + '/' + validUUID + '/edit'; + spyOn(router, 'navigate'); + comp.onCancel(data1); + fixture.detectChanges(); + expect(router.navigate).toHaveBeenCalledWith([redirectURL]); + }); + }); +}); diff --git a/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.ts b/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.ts new file mode 100644 index 0000000000..6e3a826e87 --- /dev/null +++ b/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.ts @@ -0,0 +1,71 @@ +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { RouteService } from '../../services/route.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { RemoteData } from '../../../core/data/remote-data'; +import { isNotUndefined } from '../../empty.util'; +import { first, map } from 'rxjs/operators'; +import { getSucceededRemoteData } from '../../../core/shared/operators'; +import { DataService } from '../../../core/data/data.service'; +import { NormalizedDSpaceObject } from '../../../core/cache/models/normalized-dspace-object.model'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; +import { NotificationsService } from '../../notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; + +/** + * Component representing the delete page for communities and collections + */ +@Component({ + selector: 'ds-delete-comcol', + template: '' +}) +export class DeleteComColPageComponent implements OnInit { + /** + * Frontend endpoint for this type of DSO + */ + protected frontendURL: string; + /** + * The initial DSO object + */ + public dsoRD$: Observable>; + + public constructor( + protected dsoDataService: DataService, + protected router: Router, + protected route: ActivatedRoute, + protected notifications: NotificationsService, + protected translate: TranslateService + ) { + } + + ngOnInit(): void { + this.dsoRD$ = this.route.data.pipe(first(), map((data) => data.dso)); + } + + /** + * @param {TDomain} dso The DSO to delete + * Deletes an existing DSO and redirects to the home page afterwards, showing a notification that states whether or not the deletion was successful + */ + onConfirm(dso: TDomain) { + this.dsoDataService.delete(dso) + .pipe(first()) + .subscribe((success: boolean) => { + if (success) { + const successMessage = this.translate.instant(dso.type + '.delete.notification.success'); + this.notifications.success(successMessage) + } else { + const errorMessage = this.translate.instant(dso.type + '.delete.notification.fail'); + this.notifications.error(errorMessage) + } + this.router.navigate(['/']); + }); + } + + /** + * @param {TDomain} dso The DSO for which the delete action was canceled + * When a delete is canceled, the user is redirected to the DSO's edit page + */ + onCancel(dso: TDomain) { + this.router.navigate([this.frontendURL + '/' + dso.uuid + '/edit']); + } +} diff --git a/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.spec.ts b/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.spec.ts index ae59e185a3..88c11a0b4d 100644 --- a/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.spec.ts +++ b/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.spec.ts @@ -1,6 +1,5 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { CommunityDataService } from '../../../core/data/community-data.service'; -import { RouteService } from '../../services/route.service'; import { ActivatedRoute, Router } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; import { of as observableOf } from 'rxjs'; @@ -18,15 +17,12 @@ import { DataService } from '../../../core/data/data.service'; describe('EditComColPageComponent', () => { let comp: EditComColPageComponent; let fixture: ComponentFixture>; - let communityDataService: CommunityDataService; let dsoDataService: CommunityDataService; - let routeService: RouteService; let router: Router; let community; let newCommunity; let communityDataServiceStub; - let routeServiceStub; let routerStub; let routeStub; @@ -48,20 +44,10 @@ describe('EditComColPageComponent', () => { }); communityDataServiceStub = { - findById: (uuid) => observableOf(new RemoteData(false, false, true, null, Object.assign(new Community(), { - uuid: uuid, - metadata: [{ - key: 'dc.title', - value: community.name - }] - }))), update: (com, uuid?) => observableOf(new RemoteData(false, false, true, undefined, newCommunity)) }; - routeServiceStub = { - getQueryParameterValue: (param) => observableOf(community.uuid) - }; routerStub = { navigate: (commands) => commands }; @@ -78,7 +64,6 @@ describe('EditComColPageComponent', () => { imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], providers: [ { provide: DataService, useValue: communityDataServiceStub }, - { provide: RouteService, useValue: routeServiceStub }, { provide: Router, useValue: routerStub }, { provide: ActivatedRoute, useValue: routeStub }, ], @@ -91,8 +76,6 @@ describe('EditComColPageComponent', () => { comp = fixture.componentInstance; fixture.detectChanges(); dsoDataService = (comp as any).dsoDataService; - communityDataService = (comp as any).communityDataService; - routeService = (comp as any).routeService; router = (comp as any).router; }); diff --git a/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts b/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts index 5ccbcfcc66..b669fcea54 100644 --- a/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts +++ b/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts @@ -1,6 +1,5 @@ import { Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; -import { RouteService } from '../../services/route.service'; import { ActivatedRoute, Router } from '@angular/router'; import { RemoteData } from '../../../core/data/remote-data'; import { isNotUndefined } from '../../empty.util'; @@ -19,7 +18,7 @@ import { DSpaceObject } from '../../../core/shared/dspace-object.model'; }) export class EditComColPageComponent implements OnInit { /** - * Frontend endpoint where for this type of DSP + * Frontend endpoint for this type of DSO */ protected frontendURL: string; /** @@ -29,7 +28,6 @@ export class EditComColPageComponent, - protected routeService: RouteService, protected router: Router, protected route: ActivatedRoute ) { diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index a7a45ccfd7..53cf15ab6e 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -89,6 +89,7 @@ import { MenuModule } from './menu/menu.module'; import { ComColFormComponent } from './comcol-forms/comcol-form/comcol-form.component'; import { CreateComColPageComponent } from './comcol-forms/create-comcol-page/create-comcol-page.component'; import { EditComColPageComponent } from './comcol-forms/edit-comcol-page/edit-comcol-page.component'; +import { DeleteComColPageComponent } from './comcol-forms/delete-comcol-page/delete-comcol-page.component'; import { LangSwitchComponent } from './lang-switch/lang-switch.component'; const MODULES = [ @@ -136,6 +137,7 @@ const COMPONENTS = [ ComColFormComponent, CreateComColPageComponent, EditComColPageComponent, + DeleteComColPageComponent, DsDynamicFormComponent, DsDynamicFormControlComponent, DsDynamicListComponent,