diff --git a/src/app/+my-dspace-page/my-dspace-page.module.ts b/src/app/+my-dspace-page/my-dspace-page.module.ts index 49570fec6d..ea9b4dc821 100644 --- a/src/app/+my-dspace-page/my-dspace-page.module.ts +++ b/src/app/+my-dspace-page/my-dspace-page.module.ts @@ -21,6 +21,8 @@ import { ItemSearchResultListElementSubmissionComponent } from '../shared/object import { WorkflowItemSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component'; import { PoolSearchResultDetailElementComponent } from '../shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component'; import { CollectionSelectorComponent } from './collection-selector/collection-selector.component'; +import { ClaimedApprovedSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-approved-search-result/claimed-approved-search-result-list-element.component'; +import { ClaimedDeclinedSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-search-result/claimed-declined-search-result-list-element.component'; @NgModule({ imports: [ @@ -34,6 +36,8 @@ import { CollectionSelectorComponent } from './collection-selector/collection-se WorkspaceItemSearchResultListElementComponent, WorkflowItemSearchResultListElementComponent, ClaimedSearchResultListElementComponent, + ClaimedApprovedSearchResultListElementComponent, + ClaimedDeclinedSearchResultListElementComponent, PoolSearchResultListElementComponent, ItemSearchResultDetailElementComponent, WorkspaceItemSearchResultDetailElementComponent, @@ -53,6 +57,8 @@ import { CollectionSelectorComponent } from './collection-selector/collection-se WorkspaceItemSearchResultListElementComponent, WorkflowItemSearchResultListElementComponent, ClaimedSearchResultListElementComponent, + ClaimedApprovedSearchResultListElementComponent, + ClaimedDeclinedSearchResultListElementComponent, PoolSearchResultListElementComponent, ItemSearchResultDetailElementComponent, WorkspaceItemSearchResultDetailElementComponent, diff --git a/src/app/core/tasks/claimed-task-data.service.spec.ts b/src/app/core/tasks/claimed-task-data.service.spec.ts index 98a0f5f51e..b9f38706e1 100644 --- a/src/app/core/tasks/claimed-task-data.service.spec.ts +++ b/src/app/core/tasks/claimed-task-data.service.spec.ts @@ -8,9 +8,16 @@ import { RemoteDataBuildService } from '../cache/builders/remote-data-build.serv import { NotificationsService } from '../../shared/notifications/notifications.service'; import { CoreState } from '../core.reducers'; import { ClaimedTaskDataService } from './claimed-task-data.service'; +import { of as observableOf } from 'rxjs/internal/observable/of'; +import { FindListOptions } from '../data/request.models'; +import { RequestParam } from '../cache/models/request-param.model'; +import { followLink } from '../../shared/utils/follow-link-config.model'; +import { getTestScheduler } from 'jasmine-marbles'; +import { TestScheduler } from 'rxjs/testing'; import { HttpOptions } from '../dspace-rest/dspace-rest.service'; describe('ClaimedTaskDataService', () => { + let scheduler: TestScheduler; let service: ClaimedTaskDataService; let options: HttpOptions; const taskEndpoint = 'https://rest.api/task'; @@ -45,6 +52,7 @@ describe('ClaimedTaskDataService', () => { } beforeEach(() => { + scheduler = getTestScheduler(); service = initTestService(); options = Object.create({}); let headers = new HttpHeaders(); @@ -68,6 +76,24 @@ describe('ClaimedTaskDataService', () => { }); }); + describe('claimTask', () => { + + it('should call postToEndpoint method', () => { + + spyOn(service, 'postToEndpoint').and.returnValue(observableOf(null)); + + scheduler.schedule(() => service.claimTask('scopeId', 'poolTaskHref').subscribe()); + scheduler.flush(); + + const postToEndpointOptions: HttpOptions = Object.create({}); + let headers = new HttpHeaders(); + headers = headers.append('Content-Type', 'text/uri-list'); + postToEndpointOptions.headers = headers; + + expect(service.postToEndpoint).toHaveBeenCalledWith(linkPath, 'poolTaskHref', null, postToEndpointOptions); + }); + }); + describe('returnToPoolTask', () => { it('should call deleteById method', () => { const scopeId = '1234'; @@ -79,4 +105,21 @@ describe('ClaimedTaskDataService', () => { expect(service.deleteById).toHaveBeenCalledWith(linkPath, scopeId, options); }); }); + + describe('findByItem', () => { + + it('should call searchTask method', () => { + spyOn(service, 'searchTask').and.returnValue(observableOf(null)); + + scheduler.schedule(() => service.findByItem('a0db0fde-1d12-4d43-bd0d-0f43df8d823c').subscribe()); + scheduler.flush(); + + const findListOptions = new FindListOptions(); + findListOptions.searchParams = [ + new RequestParam('uuid', 'a0db0fde-1d12-4d43-bd0d-0f43df8d823c') + ]; + + expect(service.searchTask).toHaveBeenCalledWith('findByItem', findListOptions, followLink('workflowitem')); + }); + }); }); diff --git a/src/app/core/tasks/claimed-task-data.service.ts b/src/app/core/tasks/claimed-task-data.service.ts index 5815dad6e5..99c2f51117 100644 --- a/src/app/core/tasks/claimed-task-data.service.ts +++ b/src/app/core/tasks/claimed-task-data.service.ts @@ -1,4 +1,4 @@ -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; @@ -15,6 +15,11 @@ import { ClaimedTask } from './models/claimed-task-object.model'; import { CLAIMED_TASK } from './models/claimed-task-object.resource-type'; import { ProcessTaskResponse } from './models/process-task-response'; import { TasksService } from './tasks.service'; +import { RemoteData } from '../data/remote-data'; +import { followLink } from '../../shared/utils/follow-link-config.model'; +import { FindListOptions } from '../data/request.models'; +import { RequestParam } from '../cache/models/request-param.model'; +import { HttpOptions } from '../dspace-rest/dspace-rest.service'; /** * The service handling all REST requests for ClaimedTask @@ -54,6 +59,22 @@ export class ClaimedTaskDataService extends TasksService { super(); } + /** + * Make a request to claim the given task + * + * @param scopeId + * The task id + * @return {Observable} + * Emit the server response + */ + public claimTask(scopeId: string, poolTaskHref: string): Observable { + const options: HttpOptions = Object.create({}); + let headers = new HttpHeaders(); + headers = headers.append('Content-Type', 'text/uri-list'); + options.headers = headers; + return this.postToEndpoint(this.linkPath, poolTaskHref, null, options); + } + /** * Make a request for the given task * @@ -80,4 +101,19 @@ export class ClaimedTaskDataService extends TasksService { return this.deleteById(this.linkPath, scopeId, this.makeHttpOptions()); } + /** + * Search a claimed task by item uuid. + * @param uuid + * The item uuid + * @return {Observable>} + * The server response + */ + public findByItem(uuid: string): Observable> { + const options = new FindListOptions(); + options.searchParams = [ + new RequestParam('uuid', uuid) + ]; + return this.searchTask('findByItem', options, followLink('workflowitem')); + } + } diff --git a/src/app/core/tasks/pool-task-data.service.spec.ts b/src/app/core/tasks/pool-task-data.service.spec.ts index 75255d3e0a..98461864a9 100644 --- a/src/app/core/tasks/pool-task-data.service.spec.ts +++ b/src/app/core/tasks/pool-task-data.service.spec.ts @@ -8,9 +8,16 @@ import { RemoteDataBuildService } from '../cache/builders/remote-data-build.serv import { NotificationsService } from '../../shared/notifications/notifications.service'; import { CoreState } from '../core.reducers'; import { PoolTaskDataService } from './pool-task-data.service'; +import { getTestScheduler } from 'jasmine-marbles'; +import { TestScheduler } from 'rxjs/testing'; +import { of as observableOf } from 'rxjs/internal/observable/of'; +import { followLink } from '../../shared/utils/follow-link-config.model'; +import { FindListOptions } from '../data/request.models'; +import { RequestParam } from '../cache/models/request-param.model'; import { HttpOptions } from '../dspace-rest/dspace-rest.service'; describe('PoolTaskDataService', () => { + let scheduler: TestScheduler; let service: PoolTaskDataService; let options: HttpOptions; const taskEndpoint = 'https://rest.api/task'; @@ -45,6 +52,7 @@ describe('PoolTaskDataService', () => { } beforeEach(() => { + scheduler = getTestScheduler(); service = initTestService(); options = Object.create({}); let headers = new HttpHeaders(); @@ -52,14 +60,33 @@ describe('PoolTaskDataService', () => { options.headers = headers; }); - describe('claimTask', () => { + describe('findByItem', () => { - it('should call postToEndpoint method', () => { - spyOn(service, 'postToEndpoint'); - const scopeId = '1234'; - service.claimTask(scopeId); + it('should call searchTask method', () => { + spyOn(service, 'searchTask').and.returnValue(observableOf(null)); + + scheduler.schedule(() => service.findByItem('a0db0fde-1d12-4d43-bd0d-0f43df8d823c').subscribe()); + scheduler.flush(); + + const findListOptions = new FindListOptions(); + findListOptions.searchParams = [ + new RequestParam('uuid', 'a0db0fde-1d12-4d43-bd0d-0f43df8d823c') + ]; + + expect(service.searchTask).toHaveBeenCalledWith('findByItem', findListOptions, followLink('workflowitem')); + }); + }); + + describe('getPoolTaskEndpointById', () => { + + it('should call getEndpointById method', () => { + spyOn(service, 'getEndpointById').and.returnValue(observableOf(null)); + + scheduler.schedule(() => service.getPoolTaskEndpointById('a0db0fde-1d12-4d43-bd0d-0f43df8d823c').subscribe()); + scheduler.flush(); + + expect(service.getEndpointById).toHaveBeenCalledWith('a0db0fde-1d12-4d43-bd0d-0f43df8d823c'); - expect(service.postToEndpoint).toHaveBeenCalledWith(linkPath, {}, scopeId, options); }); }); }); diff --git a/src/app/core/tasks/pool-task-data.service.ts b/src/app/core/tasks/pool-task-data.service.ts index f08274b5f1..78348d428f 100644 --- a/src/app/core/tasks/pool-task-data.service.ts +++ b/src/app/core/tasks/pool-task-data.service.ts @@ -13,8 +13,11 @@ import { RequestService } from '../data/request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { PoolTask } from './models/pool-task-object.model'; import { POOL_TASK } from './models/pool-task-object.resource-type'; -import { ProcessTaskResponse } from './models/process-task-response'; import { TasksService } from './tasks.service'; +import { RemoteData } from '../data/remote-data'; +import { followLink } from '../../shared/utils/follow-link-config.model'; +import { FindListOptions } from '../data/request.models'; +import { RequestParam } from '../cache/models/request-param.model'; /** * The service handling all REST requests for PoolTask @@ -56,14 +59,30 @@ export class PoolTaskDataService extends TasksService { } /** - * Make a request to claim the given task - * - * @param scopeId - * The task id - * @return {Observable} - * Emit the server response + * Search a pool task by item uuid. + * @param uuid + * The item uuid + * @return {Observable>} + * The server response */ - public claimTask(scopeId: string): Observable { - return this.postToEndpoint(this.linkPath, {}, scopeId, this.makeHttpOptions()); + public findByItem(uuid: string): Observable> { + const options = new FindListOptions(); + options.searchParams = [ + new RequestParam('uuid', uuid) + ]; + return this.searchTask('findByItem', options, followLink('workflowitem')); } + + /** + * Get the Href of the pool task + * + * @param poolTaskId + * the poolTask id + * @return {Observable>} + * the Href + */ + public getPoolTaskEndpointById(poolTaskId): Observable { + return this.getEndpointById(poolTaskId); + } + } diff --git a/src/app/core/tasks/tasks.service.spec.ts b/src/app/core/tasks/tasks.service.spec.ts index 3fdb7a1612..17e8cb5983 100644 --- a/src/app/core/tasks/tasks.service.spec.ts +++ b/src/app/core/tasks/tasks.service.spec.ts @@ -4,7 +4,7 @@ import { TestScheduler } from 'rxjs/testing'; import { getMockRequestService } from '../../shared/mocks/request.service.mock'; import { TasksService } from './tasks.service'; import { RequestService } from '../data/request.service'; -import { TaskDeleteRequest, TaskPostRequest } from '../data/request.models'; +import { FindListOptions, TaskDeleteRequest, TaskPostRequest } from '../data/request.models'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub'; import { TaskObject } from './models/task-object.model'; @@ -17,8 +17,8 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service'; import { ChangeAnalyzer } from '../data/change-analyzer'; import { compare, Operation } from 'fast-json-patch'; +import { of as observableOf } from 'rxjs/internal/observable/of'; import { HttpOptions } from '../dspace-rest/dspace-rest.service'; -import { getMockRemoteDataBuildService } from '../../shared/mocks/remote-data-build.service.mock'; const LINK_NAME = 'test'; @@ -57,7 +57,7 @@ describe('TasksService', () => { const linkPath = 'testTask'; const requestService = getMockRequestService(); const halService: any = new HALEndpointServiceStub(taskEndpoint); - const rdbService = getMockRemoteDataBuildService(); + const rdbService = {} as RemoteDataBuildService; const notificationsService = {} as NotificationsService; const http = {} as HttpClient; const comparator = new DummyChangeAnalyzer() as any; @@ -117,4 +117,38 @@ describe('TasksService', () => { }); }); + describe('searchTask', () => { + + it('should call findByHref with the href generated by getSearchByHref', () => { + + spyOn(service, 'getSearchByHref').and.returnValue(observableOf('generatedHref')); + spyOn(service, 'findByHref').and.returnValue(null); + + const followLinks = {}; + const options = new FindListOptions(); + options.searchParams = []; + + scheduler.schedule(() => service.searchTask('method', options, followLinks as any).subscribe()); + scheduler.flush(); + + expect(service.getSearchByHref).toHaveBeenCalledWith('method', options, followLinks as any); + expect(service.findByHref).toHaveBeenCalledWith('generatedHref'); + }); + }); + + describe('getEndpointById', () => { + + it('should call halService.getEndpoint and then getEndpointByIDHref', () => { + + spyOn(halService, 'getEndpoint').and.returnValue(observableOf('generatedHref')); + spyOn(service, 'getEndpointByIDHref').and.returnValue(null); + + scheduler.schedule(() => service.getEndpointById('scopeId').subscribe()); + scheduler.flush(); + + expect(halService.getEndpoint).toHaveBeenCalledWith(service.getLinkPath()); + expect(service.getEndpointByIDHref).toHaveBeenCalledWith('generatedHref', 'scopeId'); + }); + }); + }); diff --git a/src/app/core/tasks/tasks.service.ts b/src/app/core/tasks/tasks.service.ts index ee29b366bf..8638bb30e6 100644 --- a/src/app/core/tasks/tasks.service.ts +++ b/src/app/core/tasks/tasks.service.ts @@ -1,21 +1,24 @@ import { HttpHeaders } from '@angular/common/http'; -import { Observable } from 'rxjs'; -import { distinctUntilChanged, filter, flatMap, map, tap } from 'rxjs/operators'; +import { merge as observableMerge, Observable, of as observableOf } from 'rxjs'; +import { distinctUntilChanged, filter, find, flatMap, map, mergeMap, switchMap, tap } from 'rxjs/operators'; import { DataService } from '../data/data.service'; import { DeleteRequest, + FindListOptions, PostRequest, TaskDeleteRequest, TaskPostRequest } from '../data/request.models'; -import { isNotEmpty } from '../../shared/empty.util'; -import { HttpOptions } from '../dspace-rest/dspace-rest.service'; +import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { ProcessTaskResponse } from './models/process-task-response'; -import { getFirstCompletedRemoteData } from '../shared/operators'; +import {getFirstCompletedRemoteData, getResponseFromEntry} from '../shared/operators'; +import { ErrorResponse, MessageResponse, RestResponse } from '../cache/response.models'; import { CacheableObject } from '../cache/object-cache.reducer'; import { RemoteData } from '../data/remote-data'; +import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; +import {HttpOptions} from '../dspace-rest/dspace-rest.service'; /** * An abstract class that provides methods to handle task requests. @@ -51,7 +54,7 @@ export abstract class TasksService extends DataServic * @param resourceID * The identifier for the object */ - protected getEndpointByIDHref(endpoint, resourceID): string { + getEndpointByIDHref(endpoint, resourceID): string { return isNotEmpty(resourceID) ? `${endpoint}/${resourceID}` : `${endpoint}`; } @@ -95,10 +98,7 @@ export abstract class TasksService extends DataServic */ public deleteById(linkPath: string, scopeId: string, options?: HttpOptions): Observable { const requestId = this.requestService.generateRequestId(); - return this.halService.getEndpoint(linkPath || this.linkPath).pipe( - filter((href: string) => isNotEmpty(href)), - distinctUntilChanged(), - map((endpointURL: string) => this.getEndpointByIDHref(endpointURL, scopeId)), + return this.getEndpointById(scopeId, linkPath).pipe( map((endpointURL: string) => new TaskDeleteRequest(requestId, endpointURL, null, options)), tap((request: DeleteRequest) => this.requestService.configure(request)), flatMap((request: DeleteRequest) => this.fetchRequest(requestId)), @@ -115,4 +115,33 @@ export abstract class TasksService extends DataServic options.headers = headers; return options; } + + /** + * Get the endpoint of a task by scopeId. + * @param linkPath + * @param scopeId + */ + public getEndpointById(scopeId: string, linkPath?: string): Observable { + return this.halService.getEndpoint(linkPath || this.linkPath).pipe( + filter((href: string) => isNotEmpty(href)), + distinctUntilChanged(), + map((endpointURL: string) => this.getEndpointByIDHref(endpointURL, scopeId))); + } + + /** + * Search a task. + * @param searchMethod + * the search method + * @param options + * the find list options + * @param linksToFollow + * links to follow + */ + public searchTask(searchMethod: string, options: FindListOptions = {}, ...linksToFollow: Array>): Observable> { + const hrefObs = this.getSearchByHref(searchMethod, options, ...linksToFollow); + return hrefObs.pipe( + find((href: string) => hasValue(href)), + switchMap((href) => this.findByHref(href)) + ); + } } diff --git a/src/app/shared/mydspace-actions/claimed-task/abstract/claimed-task-actions-abstract.component.ts b/src/app/shared/mydspace-actions/claimed-task/abstract/claimed-task-actions-abstract.component.ts index dafc148147..9e75cf1f5e 100644 --- a/src/app/shared/mydspace-actions/claimed-task/abstract/claimed-task-actions-abstract.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/abstract/claimed-task-actions-abstract.component.ts @@ -1,8 +1,20 @@ -import { EventEmitter, Input, Output } from '@angular/core'; +import { Injector } from '@angular/core'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; -import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service'; -import { ProcessTaskResponse } from '../../../../core/tasks/models/process-task-response'; +import { DSpaceObject} from '../../../../core/shared/dspace-object.model'; +import { Router} from '@angular/router'; +import { NotificationsService} from '../../../notifications/notifications.service'; +import { TranslateService} from '@ngx-translate/core'; +import { SearchService} from '../../../../core/shared/search/search.service'; +import { RequestService} from '../../../../core/data/request.service'; +import { Observable} from 'rxjs'; +import { RemoteData} from '../../../../core/data/remote-data'; +import { WorkflowItem} from '../../../../core/submission/models/workflowitem.model'; +import { map, switchMap } from 'rxjs/operators'; +import { CLAIMED_TASK } from '../../../../core/tasks/models/claimed-task-object.resource-type'; +import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators'; +import { Item } from '../../../../core/shared/item.model'; +import { MyDSpaceReloadableActionsComponent } from '../../mydspace-reloadable-actions'; /** * Abstract component for rendering a claimed task's action @@ -11,28 +23,34 @@ import { ProcessTaskResponse } from '../../../../core/tasks/models/process-task- * - Add a @rendersWorkflowTaskOption annotation to your component providing the same enum value * - Optionally overwrite createBody if the request body requires more than just the option */ -export abstract class ClaimedTaskActionsAbstractComponent { +export abstract class ClaimedTaskActionsAbstractComponent extends MyDSpaceReloadableActionsComponent { + /** * The workflow task option the child component represents */ abstract option: string; - /** - * The Claimed Task to display an action for - */ - @Input() object: ClaimedTask; + object: ClaimedTask; /** - * Emits the success or failure of a processed action + * Anchor used to reload the pool task. */ - @Output() processCompleted: EventEmitter = new EventEmitter(); + itemUuid: string; + + protected constructor(protected injector: Injector, + protected router: Router, + protected notificationsService: NotificationsService, + protected translate: TranslateService, + protected searchService: SearchService, + protected requestService: RequestService) { + super(CLAIMED_TASK, injector, router, notificationsService, translate, searchService, requestService); + } /** - * A boolean representing if the operation is pending + * Submit the action on the claimed object. */ - processing$ = new BehaviorSubject(false); - - constructor(protected claimedTaskService: ClaimedTaskDataService) { + submitTask() { + this.startActionExecution().subscribe(); } /** @@ -45,17 +63,29 @@ export abstract class ClaimedTaskActionsAbstractComponent { }; } - /** - * Submit the task for this option - * While the task is submitting, processing$ is set to true and processCompleted emits the response's status when - * completed - */ - submitTask() { - this.processing$.next(true); - this.claimedTaskService.submitTask(this.object.id, this.createbody()) - .subscribe((res: ProcessTaskResponse) => { - this.processing$.next(false); - this.processCompleted.emit(res.hasSucceeded); - }); + reloadObjectExecution(): Observable | DSpaceObject> { + return this.objectDataService.findByItem(this.itemUuid as string); } + + actionExecution(): Observable { + return this.objectDataService.submitTask(this.object.id, this.createbody()) + } + + initObjects(object: ClaimedTask) { + this.object = object; + } + + /** + * Retrieve the itemUuid. + */ + initReloadAnchor() { + (this.object as any).workflowitem.pipe( + getFirstSucceededRemoteDataPayload(), + switchMap((workflowItem: WorkflowItem) => workflowItem.item.pipe(getFirstSucceededRemoteDataPayload()) + )) + .subscribe((item: Item) => { + this.itemUuid = item.uuid; + }) + } + } diff --git a/src/app/shared/mydspace-actions/claimed-task/approve/claimed-task-actions-approve.component.spec.ts b/src/app/shared/mydspace-actions/claimed-task/approve/claimed-task-actions-approve.component.spec.ts index 0b276625ba..afcc01c315 100644 --- a/src/app/shared/mydspace-actions/claimed-task/approve/claimed-task-actions-approve.component.spec.ts +++ b/src/app/shared/mydspace-actions/claimed-task/approve/claimed-task-actions-approve.component.spec.ts @@ -1,18 +1,33 @@ -import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; +import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { of as observableOf } from 'rxjs'; +import { of, of as observableOf } from 'rxjs'; import { ClaimedTaskActionsApproveComponent } from './claimed-task-actions-approve.component'; import { TranslateLoaderMock } from '../../../mocks/translate-loader.mock'; import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; import { ProcessTaskResponse } from '../../../../core/tasks/models/process-task-response'; +import { getMockSearchService } from '../../../mocks/search-service.mock'; +import { getMockRequestService } from '../../../mocks/request.service.mock'; +import { PoolTaskDataService } from '../../../../core/tasks/pool-task-data.service'; +import { NotificationsService } from '../../../notifications/notifications.service'; +import { NotificationsServiceStub } from '../../../testing/notifications-service.stub'; +import { Router } from '@angular/router'; +import { RouterStub } from '../../../testing/router.stub'; +import { SearchService } from '../../../../core/shared/search/search.service'; +import { RequestService } from '../../../../core/data/request.service'; let component: ClaimedTaskActionsApproveComponent; let fixture: ComponentFixture; +const searchService = getMockSearchService(); + +const requestService = getMockRequestService(); + +let mockPoolTaskDataService: PoolTaskDataService; + describe('ClaimedTaskActionsApproveComponent', () => { const object = Object.assign(new ClaimedTask(), { id: 'claimed-task-1' }); const claimedTaskService = jasmine.createSpyObj('claimedTaskService', { @@ -20,6 +35,7 @@ describe('ClaimedTaskActionsApproveComponent', () => { }); beforeEach(async(() => { + mockPoolTaskDataService = new PoolTaskDataService(null, null, null, null, null, null, null, null); TestBed.configureTestingModule({ imports: [ TranslateModule.forRoot({ @@ -30,7 +46,13 @@ describe('ClaimedTaskActionsApproveComponent', () => { }) ], providers: [ - { provide: ClaimedTaskDataService, useValue: claimedTaskService } + { provide: ClaimedTaskDataService, useValue: claimedTaskService }, + { provide: Injector, useValue: {} }, + { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + { provide: Router, useValue: new RouterStub() }, + { provide: SearchService, useValue: searchService }, + { provide: RequestService, useValue: requestService }, + { provide: PoolTaskDataService, useValue: mockPoolTaskDataService }, ], declarations: [ClaimedTaskActionsApproveComponent], schemas: [NO_ERRORS_SCHEMA] @@ -43,6 +65,7 @@ describe('ClaimedTaskActionsApproveComponent', () => { fixture = TestBed.createComponent(ClaimedTaskActionsApproveComponent); component = fixture.componentInstance; component.object = object; + spyOn(component, 'initReloadAnchor').and.returnValue(undefined); fixture.detectChanges(); }); @@ -66,6 +89,7 @@ describe('ClaimedTaskActionsApproveComponent', () => { beforeEach(() => { spyOn(component.processCompleted, 'emit'); + spyOn(component, 'startActionExecution').and.returnValue(of(null)); expectedBody = { [component.option]: 'true' @@ -75,12 +99,30 @@ describe('ClaimedTaskActionsApproveComponent', () => { fixture.detectChanges(); }); - it('should call claimedTaskService\'s submitTask with the expected body', () => { - expect(claimedTaskService.submitTask).toHaveBeenCalledWith(object.id, expectedBody) + it('should start the action execution', () => { + expect(component.startActionExecution).toHaveBeenCalled(); }); - it('should emit a successful processCompleted event', () => { - expect(component.processCompleted.emit).toHaveBeenCalledWith(true); + describe('actionExecution', () => { + beforeEach(() => { + component.actionExecution().subscribe(); + fixture.detectChanges(); + }); + + it('should call claimedTaskService\'s submitTask', () => { + expect(claimedTaskService.submitTask).toHaveBeenCalledWith(object.id, expectedBody) + }); + + }); + }); + + describe('reloadObjectExecution', () => { + + it('should return the component object itself', (done) => { + component.reloadObjectExecution().subscribe((val) => { + expect(val).toEqual(component.object); + done(); + }); }); }); diff --git a/src/app/shared/mydspace-actions/claimed-task/approve/claimed-task-actions-approve.component.ts b/src/app/shared/mydspace-actions/claimed-task/approve/claimed-task-actions-approve.component.ts index 8f51ac393c..1f4111a14a 100644 --- a/src/app/shared/mydspace-actions/claimed-task/approve/claimed-task-actions-approve.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/approve/claimed-task-actions-approve.component.ts @@ -1,7 +1,16 @@ -import { Component } from '@angular/core'; +import { Component, Injector } from '@angular/core'; import { ClaimedTaskActionsAbstractComponent } from '../abstract/claimed-task-actions-abstract.component'; import { rendersWorkflowTaskOption } from '../switcher/claimed-task-actions-decorator'; -import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service'; +import { Observable } from 'rxjs'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; +import { Router } from '@angular/router'; +import { NotificationsService } from '../../../notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { SearchService } from '../../../../core/shared/search/search.service'; +import { RequestService } from '../../../../core/data/request.service'; +import { ClaimedApprovedTaskSearchResult } from '../../../object-collection/shared/claimed-approved-task-search-result.model'; +import { of } from 'rxjs/internal/observable/of'; export const WORKFLOW_TASK_OPTION_APPROVE = 'submit_approve'; @@ -20,7 +29,29 @@ export class ClaimedTaskActionsApproveComponent extends ClaimedTaskActionsAbstra */ option = WORKFLOW_TASK_OPTION_APPROVE; - constructor(protected claimedTaskService: ClaimedTaskDataService) { - super(claimedTaskService); + constructor(protected injector: Injector, + protected router: Router, + protected notificationsService: NotificationsService, + protected translate: TranslateService, + protected searchService: SearchService, + protected requestService: RequestService) { + super(injector, router, notificationsService, translate, searchService, requestService); } + + reloadObjectExecution(): Observable | DSpaceObject> { + return of(this.object); + } + + convertReloadedObject(dso: DSpaceObject): DSpaceObject { + const reloadedObject = Object.assign(new ClaimedApprovedTaskSearchResult(), dso, { + indexableObject: dso + }); + return reloadedObject; + } + + // mock + actionExecution(): Observable { + return of({ hasSucceeded: true}); + } + } diff --git a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html index aa569bbfc8..6a39fd44ca 100644 --- a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html +++ b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html @@ -3,11 +3,11 @@ + (processCompleted)="this.processCompleted.emit($event)"> + (processCompleted)="this.processCompleted.emit($event)"> diff --git a/src/app/shared/mydspace-actions/claimed-task/edit-metadata/claimed-task-actions-edit-metadata.component.html b/src/app/shared/mydspace-actions/claimed-task/edit-metadata/claimed-task-actions-edit-metadata.component.html index 4a42378f7e..3f3670e17f 100644 --- a/src/app/shared/mydspace-actions/claimed-task/edit-metadata/claimed-task-actions-edit-metadata.component.html +++ b/src/app/shared/mydspace-actions/claimed-task/edit-metadata/claimed-task-actions-edit-metadata.component.html @@ -1,7 +1,7 @@ {{'submission.workflow.tasks.claimed.edit' | translate}} diff --git a/src/app/shared/mydspace-actions/claimed-task/edit-metadata/claimed-task-actions-edit-metadata.component.spec.ts b/src/app/shared/mydspace-actions/claimed-task/edit-metadata/claimed-task-actions-edit-metadata.component.spec.ts index 705563941e..acfe92501a 100644 --- a/src/app/shared/mydspace-actions/claimed-task/edit-metadata/claimed-task-actions-edit-metadata.component.spec.ts +++ b/src/app/shared/mydspace-actions/claimed-task/edit-metadata/claimed-task-actions-edit-metadata.component.spec.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; +import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; @@ -7,13 +7,28 @@ import { ClaimedTaskActionsEditMetadataComponent } from './claimed-task-actions- import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service'; import { TranslateLoaderMock } from '../../../testing/translate-loader.mock'; +import { getMockSearchService } from '../../../mocks/search-service.mock'; +import { getMockRequestService } from '../../../mocks/request.service.mock'; +import { PoolTaskDataService } from '../../../../core/tasks/pool-task-data.service'; +import { NotificationsService } from '../../../notifications/notifications.service'; +import { NotificationsServiceStub } from '../../../testing/notifications-service.stub'; +import { Router } from '@angular/router'; +import { RouterStub } from '../../../testing/router.stub'; +import { SearchService } from '../../../../core/shared/search/search.service'; +import { RequestService } from '../../../../core/data/request.service'; let component: ClaimedTaskActionsEditMetadataComponent; let fixture: ComponentFixture; +const searchService = getMockSearchService(); + +const requestService = getMockRequestService(); + +let mockPoolTaskDataService: PoolTaskDataService; + describe('ClaimedTaskActionsEditMetadataComponent', () => { const object = Object.assign(new ClaimedTask(), { id: 'claimed-task-1' }); - + mockPoolTaskDataService = new PoolTaskDataService(null, null, null, null, null, null, null, null); beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ @@ -25,7 +40,13 @@ describe('ClaimedTaskActionsEditMetadataComponent', () => { }) ], providers: [ - { provide: ClaimedTaskDataService, useValue: {} } + { provide: ClaimedTaskDataService, useValue: {} }, + { provide: Injector, useValue: {} }, + { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + { provide: Router, useValue: new RouterStub() }, + { provide: SearchService, useValue: searchService }, + { provide: RequestService, useValue: requestService }, + { provide: PoolTaskDataService, useValue: mockPoolTaskDataService }, ], declarations: [ClaimedTaskActionsEditMetadataComponent], schemas: [NO_ERRORS_SCHEMA] @@ -38,6 +59,7 @@ describe('ClaimedTaskActionsEditMetadataComponent', () => { fixture = TestBed.createComponent(ClaimedTaskActionsEditMetadataComponent); component = fixture.componentInstance; component.object = object; + spyOn(component, 'initReloadAnchor').and.returnValue(undefined); fixture.detectChanges(); }); diff --git a/src/app/shared/mydspace-actions/claimed-task/edit-metadata/claimed-task-actions-edit-metadata.component.ts b/src/app/shared/mydspace-actions/claimed-task/edit-metadata/claimed-task-actions-edit-metadata.component.ts index c0ce9cd4e5..7da189dddd 100644 --- a/src/app/shared/mydspace-actions/claimed-task/edit-metadata/claimed-task-actions-edit-metadata.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/edit-metadata/claimed-task-actions-edit-metadata.component.ts @@ -1,7 +1,11 @@ -import { Component } from '@angular/core'; +import { Component, Injector } from '@angular/core'; import { ClaimedTaskActionsAbstractComponent } from '../abstract/claimed-task-actions-abstract.component'; import { rendersWorkflowTaskOption } from '../switcher/claimed-task-actions-decorator'; -import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service'; +import { Router } from '@angular/router'; +import { NotificationsService } from '../../../notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { SearchService } from '../../../../core/shared/search/search.service'; +import { RequestService } from '../../../../core/data/request.service'; export const WORKFLOW_TASK_OPTION_EDIT_METADATA = 'submit_edit_metadata'; @@ -20,7 +24,12 @@ export class ClaimedTaskActionsEditMetadataComponent extends ClaimedTaskActionsA */ option = WORKFLOW_TASK_OPTION_EDIT_METADATA; - constructor(protected claimedTaskService: ClaimedTaskDataService) { - super(claimedTaskService); + constructor(protected injector: Injector, + protected router: Router, + protected notificationsService: NotificationsService, + protected translate: TranslateService, + protected searchService: SearchService, + protected requestService: RequestService) { + super(injector, router, notificationsService, translate, searchService, requestService); } } diff --git a/src/app/shared/mydspace-actions/claimed-task/reject/claimed-task-actions-reject.component.spec.ts b/src/app/shared/mydspace-actions/claimed-task/reject/claimed-task-actions-reject.component.spec.ts index ca78bcc0cc..d090d12099 100644 --- a/src/app/shared/mydspace-actions/claimed-task/reject/claimed-task-actions-reject.component.spec.ts +++ b/src/app/shared/mydspace-actions/claimed-task/reject/claimed-task-actions-reject.component.spec.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; -import { async, ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing'; +import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; @@ -12,12 +12,29 @@ import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.m import { of as observableOf } from 'rxjs/internal/observable/of'; import { ProcessTaskResponse } from '../../../../core/tasks/models/process-task-response'; import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service'; +import { NotificationsService } from '../../../notifications/notifications.service'; +import { NotificationsServiceStub } from '../../../testing/notifications-service.stub'; +import { Router } from '@angular/router'; +import { RouterStub } from '../../../testing/router.stub'; +import { SearchService } from '../../../../core/shared/search/search.service'; +import { RequestService } from '../../../../core/data/request.service'; +import { PoolTaskDataService } from '../../../../core/tasks/pool-task-data.service'; +import { getMockSearchService } from '../../../mocks/search-service.mock'; +import { getMockRequestService } from '../../../mocks/request.service.mock'; +import { of } from 'rxjs'; +import { ClaimedDeclinedTaskSearchResult } from '../../../object-collection/shared/claimed-declined-task-search-result.model'; let component: ClaimedTaskActionsRejectComponent; let fixture: ComponentFixture; let formBuilder: FormBuilder; let modalService: NgbModal; +const searchService = getMockSearchService(); + +const requestService = getMockRequestService(); + +let mockPoolTaskDataService: PoolTaskDataService; + describe('ClaimedTaskActionsRejectComponent', () => { const object = Object.assign(new ClaimedTask(), { id: 'claimed-task-1' }); const claimedTaskService = jasmine.createSpyObj('claimedTaskService', { @@ -25,6 +42,7 @@ describe('ClaimedTaskActionsRejectComponent', () => { }); beforeEach(async(() => { + mockPoolTaskDataService = new PoolTaskDataService(null, null, null, null, null, null, null, null); TestBed.configureTestingModule({ imports: [ NgbModule, @@ -39,6 +57,12 @@ describe('ClaimedTaskActionsRejectComponent', () => { declarations: [ClaimedTaskActionsRejectComponent], providers: [ { provide: ClaimedTaskDataService, useValue: claimedTaskService }, + { provide: Injector, useValue: {} }, + { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + { provide: Router, useValue: new RouterStub() }, + { provide: SearchService, useValue: searchService }, + { provide: RequestService, useValue: requestService }, + { provide: PoolTaskDataService, useValue: mockPoolTaskDataService }, FormBuilder, NgbModal ], @@ -55,6 +79,7 @@ describe('ClaimedTaskActionsRejectComponent', () => { modalService = TestBed.get(NgbModal); component.object = object; component.modalRef = modalService.open('ok'); + spyOn(component, 'initReloadAnchor').and.returnValue(undefined); fixture.detectChanges(); }); @@ -96,6 +121,7 @@ describe('ClaimedTaskActionsRejectComponent', () => { beforeEach(() => { spyOn(component.processCompleted, 'emit'); + spyOn(component, 'startActionExecution').and.returnValue(of(null)); expectedBody = { [component.option]: 'true', @@ -113,12 +139,39 @@ describe('ClaimedTaskActionsRejectComponent', () => { fixture.detectChanges(); }); - it('should call claimedTaskService\'s submitTask with the expected body', () => { - expect(claimedTaskService.submitTask).toHaveBeenCalledWith(object.id, expectedBody) + it('should start the action execution', () => { + expect(component.startActionExecution).toHaveBeenCalled(); }); - it('should emit a successful processCompleted event', () => { - expect(component.processCompleted.emit).toHaveBeenCalledWith(true); + describe('actionExecution', () => { + beforeEach(() => { + component.actionExecution().subscribe(); + fixture.detectChanges(); + }); + + it('should call claimedTaskService\'s submitTask', () => { + expect(claimedTaskService.submitTask).toHaveBeenCalledWith(object.id, expectedBody) + }); + }); + + }); + + describe('reloadObjectExecution', () => { + + it('should return the component object itself', (done) => { + component.reloadObjectExecution().subscribe((val) => { + expect(val).toEqual(component.object); + done(); + }); }); }); + + describe('convertReloadedObject', () => { + + it('should return a ClaimedDeclinedTaskSearchResult instance', () => { + const reloadedObject = component.convertReloadedObject(component.object); + expect(reloadedObject instanceof ClaimedDeclinedTaskSearchResult).toEqual(true); + }); + }); + }); diff --git a/src/app/shared/mydspace-actions/claimed-task/reject/claimed-task-actions-reject.component.ts b/src/app/shared/mydspace-actions/claimed-task/reject/claimed-task-actions-reject.component.ts index 46d40cbb64..267f978392 100644 --- a/src/app/shared/mydspace-actions/claimed-task/reject/claimed-task-actions-reject.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/reject/claimed-task-actions-reject.component.ts @@ -1,10 +1,19 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Injector, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { ClaimedTaskActionsAbstractComponent } from '../abstract/claimed-task-actions-abstract.component'; -import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service'; import { rendersWorkflowTaskOption } from '../switcher/claimed-task-actions-decorator'; +import { Router } from '@angular/router'; +import { NotificationsService } from '../../../notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { SearchService } from '../../../../core/shared/search/search.service'; +import { RequestService } from '../../../../core/data/request.service'; +import { Observable } from 'rxjs'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; +import { of } from 'rxjs/internal/observable/of'; +import { ClaimedDeclinedTaskSearchResult } from '../../../object-collection/shared/claimed-declined-task-search-result.model'; export const WORKFLOW_TASK_OPTION_REJECT = 'submit_reject'; @@ -33,17 +42,15 @@ export class ClaimedTaskActionsRejectComponent extends ClaimedTaskActionsAbstrac */ public modalRef: NgbModalRef; - /** - * Initialize instance variables - * - * @param {FormBuilder} formBuilder - * @param {NgbModal} modalService - * @param claimedTaskService - */ - constructor(protected claimedTaskService: ClaimedTaskDataService, + constructor(protected injector: Injector, + protected router: Router, + protected notificationsService: NotificationsService, + protected translate: TranslateService, + protected searchService: SearchService, + protected requestService: RequestService, private formBuilder: FormBuilder, private modalService: NgbModal) { - super(claimedTaskService); + super(injector, router, notificationsService, translate, searchService, requestService); } /** @@ -55,6 +62,14 @@ export class ClaimedTaskActionsRejectComponent extends ClaimedTaskActionsAbstrac }); } + /** + * Submit a reject option for the task + */ + submitTask() { + this.modalRef.close('Send Button'); + super.submitTask(); + } + /** * Create the request body for rejecting a workflow task * Includes the reason from the form @@ -64,14 +79,6 @@ export class ClaimedTaskActionsRejectComponent extends ClaimedTaskActionsAbstrac return Object.assign(super.createbody(), { reason }); } - /** - * Submit a reject option for the task - */ - submitTask() { - this.modalRef.close('Send Button'); - super.submitTask(); - } - /** * Open modal * @@ -81,4 +88,20 @@ export class ClaimedTaskActionsRejectComponent extends ClaimedTaskActionsAbstrac this.rejectForm.reset(); this.modalRef = this.modalService.open(content); } + + reloadObjectExecution(): Observable | DSpaceObject> { + return of(this.object); + } + + convertReloadedObject(dso: DSpaceObject): DSpaceObject { + const reloadedObject = Object.assign(new ClaimedDeclinedTaskSearchResult(), dso, { + indexableObject: dso + }); + return reloadedObject; + } + + // mock + actionExecution(): Observable { + return of({ hasSucceeded: true}); + } } diff --git a/src/app/shared/mydspace-actions/claimed-task/return-to-pool/claimed-task-actions-return-to-pool.component.spec.ts b/src/app/shared/mydspace-actions/claimed-task/return-to-pool/claimed-task-actions-return-to-pool.component.spec.ts index b84051e53c..5f7757aaee 100644 --- a/src/app/shared/mydspace-actions/claimed-task/return-to-pool/claimed-task-actions-return-to-pool.component.spec.ts +++ b/src/app/shared/mydspace-actions/claimed-task/return-to-pool/claimed-task-actions-return-to-pool.component.spec.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; @@ -9,10 +9,26 @@ import { of as observableOf } from 'rxjs/internal/observable/of'; import { ProcessTaskResponse } from '../../../../core/tasks/models/process-task-response'; import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service'; import { TranslateLoaderMock } from '../../../mocks/translate-loader.mock'; +import { NotificationsService } from '../../../notifications/notifications.service'; +import { NotificationsServiceStub } from '../../../testing/notifications-service.stub'; +import { Router } from '@angular/router'; +import { RouterStub } from '../../../testing/router.stub'; +import { PoolTaskDataService } from '../../../../core/tasks/pool-task-data.service'; +import { SearchService } from '../../../../core/shared/search/search.service'; +import { RequestService } from '../../../../core/data/request.service'; +import { getMockSearchService } from '../../../mocks/search-service.mock'; +import { getMockRequestService } from '../../../mocks/request.service.mock'; +import { of } from 'rxjs'; let component: ClaimedTaskActionsReturnToPoolComponent; let fixture: ComponentFixture; +const searchService = getMockSearchService(); + +const requestService = getMockRequestService(); + +let mockPoolTaskDataService: PoolTaskDataService; + describe('ClaimedTaskActionsReturnToPoolComponent', () => { const object = Object.assign(new ClaimedTask(), { id: 'claimed-task-1' }); const claimedTaskService = jasmine.createSpyObj('claimedTaskService', { @@ -20,6 +36,7 @@ describe('ClaimedTaskActionsReturnToPoolComponent', () => { }); beforeEach(async(() => { + mockPoolTaskDataService = new PoolTaskDataService(null, null, null, null, null, null, null, null); TestBed.configureTestingModule({ imports: [ TranslateModule.forRoot({ @@ -30,7 +47,13 @@ describe('ClaimedTaskActionsReturnToPoolComponent', () => { }) ], providers: [ - { provide: ClaimedTaskDataService, useValue: claimedTaskService } + { provide: ClaimedTaskDataService, useValue: claimedTaskService }, + { provide: Injector, useValue: {} }, + { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + { provide: Router, useValue: new RouterStub() }, + { provide: SearchService, useValue: searchService }, + { provide: RequestService, useValue: requestService }, + { provide: PoolTaskDataService, useValue: mockPoolTaskDataService }, ], declarations: [ClaimedTaskActionsReturnToPoolComponent], schemas: [NO_ERRORS_SCHEMA] @@ -39,12 +62,13 @@ describe('ClaimedTaskActionsReturnToPoolComponent', () => { }).compileComponents(); })); - beforeEach(() => { + beforeEach(fakeAsync(() => { fixture = TestBed.createComponent(ClaimedTaskActionsReturnToPoolComponent); component = fixture.componentInstance; component.object = object; + spyOn(component, 'initReloadAnchor').and.returnValue(undefined); fixture.detectChanges(); - }); + })); it('should display return to pool button', () => { const btn = fixture.debugElement.query(By.css('.btn-secondary')); @@ -61,11 +85,9 @@ describe('ClaimedTaskActionsReturnToPoolComponent', () => { expect(span).toBeDefined(); }); - describe('submitTask', () => { + describe('actionExecution', () => { beforeEach(() => { - spyOn(component.processCompleted, 'emit'); - - component.submitTask(); + component.actionExecution().subscribe(); fixture.detectChanges(); }); @@ -73,8 +95,19 @@ describe('ClaimedTaskActionsReturnToPoolComponent', () => { expect(claimedTaskService.returnToPoolTask).toHaveBeenCalledWith(object.id) }); - it('should emit a successful processCompleted event', () => { - expect(component.processCompleted.emit).toHaveBeenCalledWith(true); + }); + + describe('reloadObjectExecution', () => { + beforeEach(() => { + spyOn(mockPoolTaskDataService, 'findByItem').and.returnValue(of(null)); + + component.itemUuid = 'uuid'; + component.reloadObjectExecution().subscribe(); + fixture.detectChanges(); + }); + + it('should call poolTaskDataService findItem with itemUuid', () => { + expect(mockPoolTaskDataService.findByItem).toHaveBeenCalledWith('uuid'); }); }); diff --git a/src/app/shared/mydspace-actions/claimed-task/return-to-pool/claimed-task-actions-return-to-pool.component.ts b/src/app/shared/mydspace-actions/claimed-task/return-to-pool/claimed-task-actions-return-to-pool.component.ts index c53bf30fad..f875263703 100644 --- a/src/app/shared/mydspace-actions/claimed-task/return-to-pool/claimed-task-actions-return-to-pool.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/return-to-pool/claimed-task-actions-return-to-pool.component.ts @@ -1,8 +1,15 @@ -import { Component } from '@angular/core'; +import {Component, Injector} from '@angular/core'; import { ClaimedTaskActionsAbstractComponent } from '../abstract/claimed-task-actions-abstract.component'; import { rendersWorkflowTaskOption } from '../switcher/claimed-task-actions-decorator'; -import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service'; -import { ProcessTaskResponse } from '../../../../core/tasks/models/process-task-response'; +import { Observable } from 'rxjs'; +import { Router } from '@angular/router'; +import { NotificationsService } from '../../../notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { SearchService } from '../../../../core/shared/search/search.service'; +import { RequestService } from '../../../../core/data/request.service'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; +import { PoolTaskDataService } from '../../../../core/tasks/pool-task-data.service'; export const WORKFLOW_TASK_OPTION_RETURN_TO_POOL = 'return_to_pool'; @@ -21,19 +28,22 @@ export class ClaimedTaskActionsReturnToPoolComponent extends ClaimedTaskActionsA */ option = WORKFLOW_TASK_OPTION_RETURN_TO_POOL; - constructor(protected claimedTaskService: ClaimedTaskDataService) { - super(claimedTaskService); + constructor(protected injector: Injector, + protected router: Router, + protected notificationsService: NotificationsService, + protected translate: TranslateService, + protected searchService: SearchService, + protected requestService: RequestService, + private poolTaskService: PoolTaskDataService) { + super(injector, router, notificationsService, translate, searchService, requestService); } - /** - * Submit a return to pool option for the task - */ - submitTask() { - this.processing$.next(true); - this.claimedTaskService.returnToPoolTask(this.object.id) - .subscribe((res: ProcessTaskResponse) => { - this.processing$.next(false); - this.processCompleted.emit(res.hasSucceeded); - }); + reloadObjectExecution(): Observable | DSpaceObject> { + return this.poolTaskService.findByItem(this.itemUuid); } + + actionExecution(): Observable { + return this.objectDataService.returnToPoolTask(this.object.id); + } + } diff --git a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts index d8c8ecccec..34142fc8be 100644 --- a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts @@ -14,6 +14,7 @@ import { ClaimedTaskActionsDirective } from './claimed-task-actions.directive'; import { ClaimedTaskActionsAbstractComponent } from '../abstract/claimed-task-actions-abstract.component'; import { hasValue } from '../../../empty.util'; import { Subscription } from 'rxjs/internal/Subscription'; +import { MyDSpaceActionsResult } from '../../mydspace-actions'; @Component({ selector: 'ds-claimed-task-actions-loader', @@ -38,7 +39,7 @@ export class ClaimedTaskActionsLoaderComponent implements OnInit, OnDestroy { /** * Emits the success or failure of a processed action */ - @Output() processCompleted: EventEmitter = new EventEmitter(); + @Output() processCompleted = new EventEmitter(); /** * Directive to determine where the dynamic child component is located @@ -69,7 +70,7 @@ export class ClaimedTaskActionsLoaderComponent implements OnInit, OnDestroy { const componentInstance = (componentRef.instance as ClaimedTaskActionsAbstractComponent); componentInstance.object = this.object; if (hasValue(componentInstance.processCompleted)) { - this.subs.push(componentInstance.processCompleted.subscribe((success) => this.processCompleted.emit(success))); + this.subs.push(componentInstance.processCompleted.subscribe((result) => this.processCompleted.emit(result))); } } } diff --git a/src/app/shared/mydspace-actions/mydspace-actions.ts b/src/app/shared/mydspace-actions/mydspace-actions.ts index c83fa35b24..373cab9d35 100644 --- a/src/app/shared/mydspace-actions/mydspace-actions.ts +++ b/src/app/shared/mydspace-actions/mydspace-actions.ts @@ -1,5 +1,5 @@ import { Router } from '@angular/router'; -import { Injector, Input } from '@angular/core'; +import { EventEmitter, Injector, Input, Output } from '@angular/core'; import { find, take, tap } from 'rxjs/operators'; @@ -12,9 +12,14 @@ import { NotificationOptions } from '../notifications/models/notification-option import { NotificationsService } from '../notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; import { RequestService } from '../../core/data/request.service'; -import { Subscription } from 'rxjs'; +import { BehaviorSubject, Subscription } from 'rxjs'; import { SearchService } from '../../core/shared/search/search.service'; +export interface MyDSpaceActionsResult { + result: boolean, + reloadedObject: DSpaceObject, +} + /** * Abstract class for all different representations of mydspace actions */ @@ -26,7 +31,18 @@ export abstract class MyDSpaceActionsComponent(); + + /** + * A boolean representing if an operation is pending + * @type {BehaviorSubject} + */ + public processing$ = new BehaviorSubject(false); + + /** + * Instance of DataService related to mydspace object */ protected objectDataService: TService; diff --git a/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts b/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts new file mode 100644 index 0000000000..4fe3afd833 --- /dev/null +++ b/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts @@ -0,0 +1,137 @@ +import { Router } from '@angular/router'; +import { Injector, OnInit } from '@angular/core'; + +import { map, switchMap, tap} from 'rxjs/operators'; + +import { RemoteData } from '../../core/data/remote-data'; +import { DataService } from '../../core/data/data.service'; +import { DSpaceObject } from '../../core/shared/dspace-object.model'; +import { ResourceType } from '../../core/shared/resource-type'; +import { NotificationOptions } from '../notifications/models/notification-options.model'; +import { NotificationsService } from '../notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { RequestService } from '../../core/data/request.service'; +import { SearchService } from '../../core/shared/search/search.service'; +import { Observable} from 'rxjs/internal/Observable'; +import { of} from 'rxjs/internal/observable/of'; +import { ProcessTaskResponse } from '../../core/tasks/models/process-task-response'; +import { getFirstSucceededRemoteDataPayload } from '../../core/shared/operators'; +import { getSearchResultFor } from '../search/search-result-element-decorator'; +import { MyDSpaceActionsComponent } from './mydspace-actions'; + +/** + * Abstract class for all different representations of mydspace actions + */ +export abstract class MyDSpaceReloadableActionsComponent> + extends MyDSpaceActionsComponent implements OnInit { + + protected constructor( + protected objectType: ResourceType, + protected injector: Injector, + protected router: Router, + protected notificationsService: NotificationsService, + protected translate: TranslateService, + protected searchService: SearchService, + protected requestService: RequestService) { + super(objectType, injector, router, notificationsService, translate, searchService, requestService); + } + + /** + * Perform the actual implementation of this reloadable action. + */ + abstract actionExecution(): Observable; + + /** + * Reload the object (typically by a remote call). + */ + abstract reloadObjectExecution(): Observable | DSpaceObject>; + + ngOnInit() { + this.initReloadAnchor(); + this.initObjects(this.object); + } + + /** + * Start the execution of the action. + * 1. performAction + * 2. reload of the object + * 3. notification + */ + startActionExecution(): Observable { + this.processing$.next(true); + return this.actionExecution().pipe( + switchMap((res: ProcessTaskResponse) => { + if (res.hasSucceeded) { + return this._reloadObject().pipe( + tap( + (reloadedObject) => { + this.processing$.next(false); + this.handleReloadableActionResponse(res.hasSucceeded, reloadedObject); + return reloadedObject; + }) + ) + } else { + this.processing$.next(false); + this.handleReloadableActionResponse(res.hasSucceeded, null); + return of(null); + } + })); + } + + /** + * Handle the action response and show properly notifications. + * + * @param result + * true on success, false otherwise + * @param reloadedObject + * the reloadedObject + */ + handleReloadableActionResponse(result: boolean, reloadedObject: DSpaceObject): void { + if (result) { + if (reloadedObject) { + this.processCompleted.emit({result, reloadedObject}); + } else { + this.reload(); + } + this.notificationsService.success(null, + this.translate.get('submission.workflow.tasks.generic.success'), + new NotificationOptions(5000, false)); + } else { + this.notificationsService.error(null, + this.translate.get('submission.workflow.tasks.generic.error'), + new NotificationOptions(20000, true)); + } + } + + /** + * Hook called on init to initialized the required information used to reload the object. + */ + // tslint:disable-next-line:no-empty + initReloadAnchor() {} + + /** + * Convert the reloadedObject to the Type required by this action. + * @param dso + */ + convertReloadedObject(dso: DSpaceObject): DSpaceObject { + const constructor = getSearchResultFor((dso as any).constructor); + const reloadedObject = Object.assign(new constructor(), dso, { + indexableObject: dso + }); + return reloadedObject; + } + + /** + * Retrieve the refreshed object and transform it to a reloadedObject. + * @param dso + */ + private _reloadObject(): Observable { + return this.reloadObjectExecution().pipe( + switchMap((res) => { + return res instanceof RemoteData ? of(res).pipe(getFirstSucceededRemoteDataPayload()) : of(res) + })).pipe(map((dso) => { + return this.convertReloadedObject(dso); + })) + } + +} diff --git a/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.html b/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.html index 6f4ffffad3..214f85ed5b 100644 --- a/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.html +++ b/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.html @@ -1,8 +1,8 @@ diff --git a/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.spec.ts b/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.spec.ts index 5b0fb2058c..3b4eb23430 100644 --- a/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.spec.ts +++ b/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.spec.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core'; -import { async, ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; import { By } from '@angular/platform-browser'; @@ -16,11 +16,17 @@ import { PoolTaskDataService } from '../../../core/tasks/pool-task-data.service' import { PoolTaskActionsComponent } from './pool-task-actions.component'; import { PoolTask } from '../../../core/tasks/models/pool-task-object.model'; import { WorkflowItem } from '../../../core/submission/models/workflowitem.model'; -import { createSuccessfulRemoteDataObject } from '../../remote-data.utils'; +import { createFailedRemoteDataObject, createSuccessfulRemoteDataObject } from '../../remote-data.utils'; import { getMockRequestService } from '../../mocks/request.service.mock'; import { RequestService } from '../../../core/data/request.service'; import { getMockSearchService } from '../../mocks/search-service.mock'; import { SearchService } from '../../../core/shared/search/search.service'; +import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service'; +import { PoolTaskSearchResult } from '../../object-collection/shared/pool-task-search-result.model'; +import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response'; + +let mockDataService: PoolTaskDataService; +let mockClaimedTaskDataService: ClaimedTaskDataService; let component: PoolTaskActionsComponent; let fixture: ComponentFixture; @@ -29,10 +35,6 @@ let mockObject: PoolTask; let notificationsServiceStub: NotificationsServiceStub; let router: RouterStub; -const mockDataService = jasmine.createSpyObj('PoolTaskDataService', { - claimTask: jasmine.createSpy('claimTask') -}); - const searchService = getMockSearchService(); const requestServce = getMockRequestService(); @@ -73,6 +75,8 @@ mockObject = Object.assign(new PoolTask(), { workflowitem: observableOf(rdWorkfl describe('PoolTaskActionsComponent', () => { beforeEach(async(() => { + mockDataService = new PoolTaskDataService(null, null, null, null, null, null, null, null) + mockClaimedTaskDataService = new ClaimedTaskDataService(null, null, null, null, null, null, null, null); TestBed.configureTestingModule({ imports: [ TranslateModule.forRoot({ @@ -88,6 +92,7 @@ describe('PoolTaskActionsComponent', () => { { provide: NotificationsService, useValue: new NotificationsServiceStub() }, { provide: Router, useValue: new RouterStub() }, { provide: PoolTaskDataService, useValue: mockDataService }, + { provide: ClaimedTaskDataService, useValue: mockClaimedTaskDataService }, { provide: SearchService, useValue: searchService }, { provide: RequestService, useValue: requestServce } ], @@ -128,63 +133,182 @@ describe('PoolTaskActionsComponent', () => { expect(btn).toBeDefined(); }); - it('should call claimTask method on claim', fakeAsync(() => { - spyOn(component, 'reload'); - mockDataService.claimTask.and.returnValue(observableOf({hasSucceeded: true})); + it('should call claim task with href of getPoolTaskEndpointById', ((done) => { - component.claim(); - fixture.detectChanges(); + const poolTaskHref = 'poolTaskHref'; + const remoteClaimTaskResponse: any = new ProcessTaskResponse(true, null, null); + const remoteReloadedObjectResponse: any = createSuccessfulRemoteDataObject(new PoolTask()); - fixture.whenStable().then(() => { - expect(mockDataService.claimTask).toHaveBeenCalledWith(mockObject.id); - }); + spyOn(mockDataService, 'getPoolTaskEndpointById').and.returnValue(observableOf(poolTaskHref)); + spyOn(mockClaimedTaskDataService, 'claimTask').and.returnValue(observableOf(remoteClaimTaskResponse)); + spyOn(mockClaimedTaskDataService, 'findByItem').and.returnValue(observableOf(remoteReloadedObjectResponse)); - })); + (component as any).objectDataService = mockDataService; - it('should display a success notification on claim success', async(() => { - spyOn(component, 'reload'); - mockDataService.claimTask.and.returnValue(observableOf({hasSucceeded: true})); + spyOn(component, 'handleReloadableActionResponse').and.callThrough(); - component.claim(); - fixture.detectChanges(); + component.startActionExecution().subscribe( (result) => { + + expect(mockDataService.getPoolTaskEndpointById).toHaveBeenCalledWith(mockObject.id); + expect(mockClaimedTaskDataService.claimTask).toHaveBeenCalledWith(mockObject.id, poolTaskHref); + expect(mockClaimedTaskDataService.findByItem).toHaveBeenCalledWith(component.itemUuid); + + expect(result instanceof PoolTaskSearchResult).toBeTrue(); + + expect(component.handleReloadableActionResponse).toHaveBeenCalledWith(true, result); - fixture.whenStable().then(() => { expect(notificationsServiceStub.success).toHaveBeenCalled(); - }); + + done(); + }) + })); - it('should reload page on claim success', async(() => { - spyOn(router, 'navigateByUrl'); - router.url = 'test.url/test'; - mockDataService.claimTask.and.returnValue(observableOf({hasSucceeded: true})); + // general reload assertion + describe('on reload action init', () => { - component.claim(); - fixture.detectChanges(); - - fixture.whenStable().then(() => { - expect(router.navigateByUrl).toHaveBeenCalledWith('test.url/test'); + beforeEach(() => { + spyOn(component, 'initReloadAnchor').and.returnValue(null); + spyOn(component, 'initObjects'); }); - })); - it('should display an error notification on claim failure', async(() => { - mockDataService.claimTask.and.returnValue(observableOf({hasSucceeded: false})); + it('should call initReloadAnchor and initObjects on init', async(() => { + component.ngOnInit(); - component.claim(); - fixture.detectChanges(); + fixture.detectChanges(); - fixture.whenStable().then(() => { - expect(notificationsServiceStub.error).toHaveBeenCalled(); + fixture.whenStable().then(() => { + expect(component.initReloadAnchor).toHaveBeenCalled(); + expect(component.initObjects).toHaveBeenCalled(); + }); + + })); + + }) + + describe('on action execution fail', () => { + + let remoteClaimTaskErrorResponse; + + beforeEach(() => { + + mockDataService = new PoolTaskDataService(null, null, null, null, null, null, null, null); + + const poolTaskHref = 'poolTaskHref'; + remoteClaimTaskErrorResponse = new ProcessTaskResponse(false, null, null); + const remoteReloadedObjectResponse: any = createSuccessfulRemoteDataObject(new PoolTask()); + + spyOn(mockDataService, 'getPoolTaskEndpointById').and.returnValue(observableOf(poolTaskHref)); + spyOn(mockClaimedTaskDataService, 'findByItem').and.returnValue(observableOf(remoteReloadedObjectResponse)); + spyOn(mockClaimedTaskDataService, 'claimTask').and.returnValue(observableOf(remoteClaimTaskErrorResponse)); + spyOn(component, 'reloadObjectExecution').and.callThrough(); + spyOn(component.processCompleted, 'emit').and.callThrough(); + + (component as any).objectDataService = mockDataService; }); - })); - it('should clear the object cache by href', async(() => { - component.reload(); - fixture.detectChanges(); + it('should show error notification', (done) => { - fixture.whenStable().then(() => { - expect(searchService.getEndpoint).toHaveBeenCalled(); - expect(requestServce.removeByHrefSubstring).toHaveBeenCalledWith('discover/search/objects'); + component.startActionExecution().subscribe( (result) => { + expect(notificationsServiceStub.error).toHaveBeenCalled(); + done(); + }) }); - })); + + it('should not call reloadObject', (done) => { + + component.startActionExecution().subscribe( (result) => { + expect(component.reloadObjectExecution).not.toHaveBeenCalled(); + done(); + }) + + }); + + it('should not emit processCompleted', (done) => { + + component.startActionExecution().subscribe( (result) => { + expect(component.processCompleted.emit).not.toHaveBeenCalled(); + done(); + }) + + }); + + }); + + describe('on action execution success', () => { + + beforeEach(() => { + + mockDataService = new PoolTaskDataService(null, null, null, null, null, null, null, null); + + const poolTaskHref = 'poolTaskHref'; + const remoteClaimTaskResponse: any = new ProcessTaskResponse(true, null, null); + const remoteReloadedObjectResponse: any = createSuccessfulRemoteDataObject(new PoolTask()); + + spyOn(mockDataService, 'getPoolTaskEndpointById').and.returnValue(observableOf(poolTaskHref)); + spyOn(mockClaimedTaskDataService, 'findByItem').and.returnValue(observableOf(remoteReloadedObjectResponse)); + spyOn(mockClaimedTaskDataService, 'claimTask').and.returnValue(observableOf(remoteClaimTaskResponse)); + spyOn(component, 'reloadObjectExecution').and.callThrough(); + spyOn(component, 'convertReloadedObject').and.callThrough(); + spyOn(component.processCompleted, 'emit').and.callThrough(); + + (component as any).objectDataService = mockDataService; + }); + + it('should reloadObject in case of action execution success', (done) => { + + component.startActionExecution().subscribe( (result) => { + expect(component.reloadObjectExecution).toHaveBeenCalled(); + done(); + }) + }); + + it('should convert the reloaded object', (done) => { + + component.startActionExecution().subscribe( (result) => { + expect(component.convertReloadedObject).toHaveBeenCalled(); + done(); + }) + }); + + it('should emit the reloaded object in case of success', (done) => { + + component.startActionExecution().subscribe( (result) => { + expect(component.processCompleted.emit).toHaveBeenCalledWith({result: true, reloadedObject: result as any}); + done(); + }) + }); + + }); + + describe('on action execution success but without a reloadedObject', () => { + + beforeEach(() => { + + mockDataService = new PoolTaskDataService(null, null, null, null, null, null, null, null); + + const poolTaskHref = 'poolTaskHref'; + const remoteClaimTaskResponse: any = new ProcessTaskResponse(true, null, null); + const remoteReloadedObjectResponse: any = createFailedRemoteDataObject(); + + spyOn(mockDataService, 'getPoolTaskEndpointById').and.returnValue(observableOf(poolTaskHref)); + spyOn(mockClaimedTaskDataService, 'findByItem').and.returnValue(observableOf(remoteReloadedObjectResponse)); + spyOn(mockClaimedTaskDataService, 'claimTask').and.returnValue(observableOf(remoteClaimTaskResponse)); + + spyOn(component, 'convertReloadedObject').and.returnValue(null); + spyOn(component, 'reload').and.returnValue(null); + + (component as any).objectDataService = mockDataService; + }); + + it('should call reload method', (done) => { + + component.startActionExecution().subscribe( (result) => { + expect(component.reload).toHaveBeenCalled(); + done(); + }) + }); + + }); }); diff --git a/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.ts b/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.ts index 892298d2f7..2a72d1a549 100644 --- a/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.ts +++ b/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.ts @@ -1,20 +1,24 @@ import { Component, Injector, Input } from '@angular/core'; import { Router } from '@angular/router'; -import { BehaviorSubject, Observable } from 'rxjs'; -import { filter, map } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import { filter, map, switchMap } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; import { WorkflowItem } from '../../../core/submission/models/workflowitem.model'; -import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response'; import { RemoteData } from '../../../core/data/remote-data'; import { PoolTask } from '../../../core/tasks/models/pool-task-object.model'; import { PoolTaskDataService } from '../../../core/tasks/pool-task-data.service'; import { isNotUndefined } from '../../empty.util'; -import { MyDSpaceActionsComponent } from '../mydspace-actions'; import { NotificationsService } from '../../notifications/notifications.service'; import { RequestService } from '../../../core/data/request.service'; import { SearchService } from '../../../core/shared/search/search.service'; +import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service'; +import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators'; +import { Item } from '../../../core/shared/item.model'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; +import { MyDSpaceReloadableActionsComponent } from '../mydspace-reloadable-actions'; +import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response'; /** * This component represents mydspace actions related to PoolTask object. @@ -24,24 +28,23 @@ import { SearchService } from '../../../core/shared/search/search.service'; styleUrls: ['./pool-task-actions.component.scss'], templateUrl: './pool-task-actions.component.html', }) -export class PoolTaskActionsComponent extends MyDSpaceActionsComponent { +export class PoolTaskActionsComponent extends MyDSpaceReloadableActionsComponent { /** * The PoolTask object */ @Input() object: PoolTask; - /** - * A boolean representing if a claim operation is pending - * @type {BehaviorSubject} - */ - public processingClaim$ = new BehaviorSubject(false); - /** * The workflowitem object that belonging to the PoolTask object */ public workflowitem$: Observable; + /** + * Anchor used to reload the pool task. + */ + public itemUuid: string; + /** * Initialize instance variables * @@ -55,6 +58,7 @@ export class PoolTaskActionsComponent extends MyDSpaceActionsComponent) => rd.payload)); } - /** - * Claim the task. - */ - claim() { - this.processingClaim$.next(true); - this.objectDataService.claimTask(this.object.id) - .subscribe((res: ProcessTaskResponse) => { - this.handleActionResponse(res.hasSucceeded); - this.processingClaim$.next(false); - }); + actionExecution(): Observable { + return this.objectDataService.getPoolTaskEndpointById(this.object.id) + .pipe(switchMap((poolTaskHref) => { + return this.claimedTaskService.claimTask(this.object.id, poolTaskHref); + })) } + + reloadObjectExecution(): Observable | DSpaceObject> { + return this.claimedTaskService.findByItem(this.itemUuid); + } + + /** + * Retrieve the itemUuid. + */ + initReloadAnchor() { + (this.object as any).workflowitem.pipe( + getFirstSucceededRemoteDataPayload(), + switchMap((workflowItem: WorkflowItem) => workflowItem.item.pipe(getFirstSucceededRemoteDataPayload()) + )) + .subscribe((item: Item) => { + this.itemUuid = item.uuid; + }) + } + } diff --git a/src/app/shared/object-collection/shared/claimed-approved-task-search-result.model.ts b/src/app/shared/object-collection/shared/claimed-approved-task-search-result.model.ts new file mode 100644 index 0000000000..7cacd87048 --- /dev/null +++ b/src/app/shared/object-collection/shared/claimed-approved-task-search-result.model.ts @@ -0,0 +1,8 @@ +import { ClaimedTask } from '../../../core/tasks/models/claimed-task-object.model'; +import { SearchResult } from '../../search/search-result.model'; + +/** + * Represents a search result object of an Approved ClaimedTask object + */ +export class ClaimedApprovedTaskSearchResult extends SearchResult { +} diff --git a/src/app/shared/object-collection/shared/claimed-declined-task-search-result.model.ts b/src/app/shared/object-collection/shared/claimed-declined-task-search-result.model.ts new file mode 100644 index 0000000000..ff775be909 --- /dev/null +++ b/src/app/shared/object-collection/shared/claimed-declined-task-search-result.model.ts @@ -0,0 +1,8 @@ +import { ClaimedTask } from '../../../core/tasks/models/claimed-task-object.model'; +import { SearchResult } from '../../search/search-result.model'; + +/** + * Represents a search result object of a Declined ClaimedTask object + */ +export class ClaimedDeclinedTaskSearchResult extends SearchResult { +} diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.scss b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.scss new file mode 100644 index 0000000000..b9bc65ea45 --- /dev/null +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.scss @@ -0,0 +1,3 @@ +:host { + width: 100%; +} diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts index de510af0bb..2ede0fabfc 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts @@ -1,4 +1,4 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import {async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing'; import { ChangeDetectionStrategy, ComponentFactoryResolver, NO_ERRORS_SCHEMA } from '@angular/core'; import { ListableObjectComponentLoaderComponent } from './listable-object-component-loader.component'; import { ListableObject } from '../listable-object.model'; @@ -9,6 +9,7 @@ import * as listableObjectDecorators from './listable-object.decorator'; import { ItemListElementComponent } from '../../../object-list/item-list-element/item-types/item/item-list-element.component'; import { ListableObjectDirective } from './listable-object.directive'; import { spyOnExported } from '../../../testing/utils.test'; +import { By } from '@angular/platform-browser'; const testType = 'TestType'; const testContext = Context.Search; @@ -55,4 +56,20 @@ describe('ListableObjectComponentLoaderComponent', () => { expect(listableObjectDecorators.getListableObjectComponent).toHaveBeenCalledWith([testType], testViewMode, testContext); }) }); + + describe('When a reloadedObject is emitted', () => { + + it('should re-instantiate the listable component ', fakeAsync(() => { + + spyOn((comp as any), 'instantiateComponent').and.returnValue(null); + + const listableComponent = fixture.debugElement.query(By.css('ds-item-list-element')).componentInstance; + const reloadedObject: any = 'object'; + (listableComponent as any).reloadedObject.emit(reloadedObject); + tick(); + + expect((comp as any).instantiateComponent).toHaveBeenCalledWith(reloadedObject); + })) + + }); }); diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts index 4525c9d5e0..e7a348fc2b 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts @@ -1,4 +1,4 @@ -import { Component, ComponentFactoryResolver, Input, OnInit, ViewChild } from '@angular/core'; +import { Component, ComponentFactoryResolver, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { ListableObject } from '../listable-object.model'; import { ViewMode } from '../../../../core/shared/view-mode.model'; import { Context } from '../../../../core/shared/context.model'; @@ -6,16 +6,19 @@ import { getListableObjectComponent } from './listable-object.decorator'; import { GenericConstructor } from '../../../../core/shared/generic-constructor'; import { ListableObjectDirective } from './listable-object.directive'; import { CollectionElementLinkType } from '../../collection-element-link.type'; +import { hasValue } from '../../../empty.util'; +import { Subscription } from 'rxjs/internal/Subscription'; +import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; @Component({ selector: 'ds-listable-object-component-loader', - // styleUrls: ['./listable-object-component-loader.component.scss'], + styleUrls: ['./listable-object-component-loader.component.scss'], templateUrl: './listable-object-component-loader.component.html' }) /** * Component for determining what component to use depending on the item's relationship type (relationship.type) */ -export class ListableObjectComponentLoaderComponent implements OnInit { +export class ListableObjectComponentLoaderComponent implements OnInit, OnDestroy { /** * The item or metadata to determine the component for */ @@ -61,6 +64,12 @@ export class ListableObjectComponentLoaderComponent implements OnInit { */ @ViewChild(ListableObjectDirective, {static: true}) listableObjectDirective: ListableObjectDirective; + /** + * Array to track all subscriptions and unsubscribe them onDestroy + * @type {Array} + */ + protected subs: Subscription[] = []; + constructor(private componentFactoryResolver: ComponentFactoryResolver) { } @@ -68,13 +77,23 @@ export class ListableObjectComponentLoaderComponent implements OnInit { * Setup the dynamic child component */ ngOnInit(): void { - const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent()); + this.instantiateComponent(this.object); + } + + ngOnDestroy() { + this.subs + .filter((subscription) => hasValue(subscription)) + .forEach((subscription) => subscription.unsubscribe()); + } + + private instantiateComponent(object) { + const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent(object)); const viewContainerRef = this.listableObjectDirective.viewContainerRef; viewContainerRef.clear(); const componentRef = viewContainerRef.createComponent(componentFactory); - (componentRef.instance as any).object = this.object; + (componentRef.instance as any).object = object; (componentRef.instance as any).index = this.index; (componentRef.instance as any).linkType = this.linkType; (componentRef.instance as any).listID = this.listID; @@ -82,13 +101,21 @@ export class ListableObjectComponentLoaderComponent implements OnInit { (componentRef.instance as any).context = this.context; (componentRef.instance as any).viewMode = this.viewMode; (componentRef.instance as any).value = this.value; + + if ((componentRef.instance as any).reloadedObject) { + this.subs.push((componentRef.instance as any).reloadedObject.subscribe((reloadedObject: DSpaceObject) => { + if (reloadedObject) { + this.instantiateComponent(reloadedObject); + } + })); + } } /** * Fetch the component depending on the item's relationship type, view mode and context * @returns {GenericConstructor} */ - private getComponent(): GenericConstructor { - return getListableObjectComponent(this.object.getRenderTypes(), this.viewMode, this.context) + private getComponent(object): GenericConstructor { + return getListableObjectComponent(object.getRenderTypes(), this.viewMode, this.context) } } diff --git a/src/app/shared/object-collection/shared/mydspace-item-status/my-dspace-item-status-type.ts b/src/app/shared/object-collection/shared/mydspace-item-status/my-dspace-item-status-type.ts index 48a0a6f4a3..5cf4d91b20 100644 --- a/src/app/shared/object-collection/shared/mydspace-item-status/my-dspace-item-status-type.ts +++ b/src/app/shared/object-collection/shared/mydspace-item-status/my-dspace-item-status-type.ts @@ -3,5 +3,7 @@ export enum MyDspaceItemStatusType { VALIDATION = 'mydspace.status.validation', WAITING_CONTROLLER = 'mydspace.status.waiting-for-controller', WORKSPACE = 'mydspace.status.workspace', - ARCHIVED = 'mydspace.status.archived' + ARCHIVED = 'mydspace.status.archived', + DECLINED = 'mydspace.status.declined', + APPROVED = 'mydspace.status.approved', } diff --git a/src/app/shared/object-collection/shared/object-collection-element/abstract-listable-element.component.ts b/src/app/shared/object-collection/shared/object-collection-element/abstract-listable-element.component.ts index 2c944b92ce..7d4e107b2b 100644 --- a/src/app/shared/object-collection/shared/object-collection-element/abstract-listable-element.component.ts +++ b/src/app/shared/object-collection/shared/object-collection-element/abstract-listable-element.component.ts @@ -1,14 +1,16 @@ -import { Component, Input } from '@angular/core'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; import { ListableObject } from '../listable-object.model'; import { CollectionElementLinkType } from '../../collection-element-link.type'; import { Context } from '../../../../core/shared/context.model'; import { ViewMode } from '../../../../core/shared/view-mode.model'; +import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; @Component({ selector: 'ds-abstract-object-element', template: ``, }) export class AbstractListableElementComponent { + /** * The object to render in this list element */ @@ -49,6 +51,11 @@ export class AbstractListableElementComponent { */ @Input() viewMode: ViewMode; + /** + * Emit when the object has been reloaded. + */ + @Output() reloadedObject = new EventEmitter(); + /** * The available link types */ diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component.html b/src/app/shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component.html index a03d8c96fe..1d8f599e65 100644 --- a/src/app/shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component.html +++ b/src/app/shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component.html @@ -6,5 +6,5 @@ [status]="status"> - + diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component.spec.ts b/src/app/shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component.spec.ts index a2010691b6..66173fb1dd 100644 --- a/src/app/shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component.spec.ts +++ b/src/app/shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component.spec.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { of as observableOf } from 'rxjs'; @@ -14,6 +14,7 @@ import { ClaimedTaskSearchResult } from '../../../object-collection/shared/claim import { VarDirective } from '../../../utils/var.directive'; import { LinkService } from '../../../../core/cache/builders/link.service'; import { getMockLinkService } from '../../../mocks/link-service.mock'; +import { By } from '@angular/platform-browser'; let component: ClaimedTaskSearchResultDetailElementComponent; let fixture: ComponentFixture; @@ -98,4 +99,16 @@ describe('ClaimedTaskSearchResultDetailElementComponent', () => { it('should have properly status', () => { expect(component.status).toEqual(MyDspaceItemStatusType.VALIDATION); }); + + it('should forward claimed-task-actions processComplete event to reloadObject event emitter', fakeAsync(() => { + spyOn(component.reloadedObject, 'emit').and.callThrough(); + const actionPayload: any = { reloadedObject: {}}; + + const actionsComponent = fixture.debugElement.query(By.css('ds-claimed-task-actions')); + actionsComponent.triggerEventHandler('processCompleted', actionPayload) + tick(); + + expect(component.reloadedObject.emit).toHaveBeenCalledWith(actionPayload.reloadedObject); + + })); }); diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.html b/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.html index 61c897e8d5..232b54d4d9 100644 --- a/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.html +++ b/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.html @@ -5,5 +5,5 @@ [showSubmitter]="showSubmitter" [status]="status"> - + diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.spec.ts b/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.spec.ts index 0c2b3a0a70..294b9b06f6 100644 --- a/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.spec.ts +++ b/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.spec.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { of as observableOf } from 'rxjs'; @@ -14,6 +14,7 @@ import { PoolTaskSearchResult } from '../../../object-collection/shared/pool-tas import { VarDirective } from '../../../utils/var.directive'; import { LinkService } from '../../../../core/cache/builders/link.service'; import { getMockLinkService } from '../../../mocks/link-service.mock'; +import { By } from '@angular/platform-browser'; let component: PoolSearchResultDetailElementComponent; let fixture: ComponentFixture; @@ -99,4 +100,15 @@ describe('PoolSearchResultDetailElementComponent', () => { it('should have properly status', () => { expect(component.status).toEqual(MyDspaceItemStatusType.WAITING_CONTROLLER); }); + + it('should forward pool-task-actions processCompleted event to the reloadedObject event emitter', fakeAsync(() => { + spyOn(component.reloadedObject, 'emit').and.callThrough(); + const actionPayload: any = { reloadedObject: {}}; + const actionsComponents = fixture.debugElement.query(By.css('ds-pool-task-actions')); + actionsComponents.triggerEventHandler('processCompleted', actionPayload) + tick(); + + expect(component.reloadedObject.emit).toHaveBeenCalledWith(actionPayload.reloadedObject); + + })); }); diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/abstract-claimed-search-result-list-element.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/abstract-claimed-search-result-list-element.component.ts new file mode 100644 index 0000000000..3dab2f844f --- /dev/null +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/abstract-claimed-search-result-list-element.component.ts @@ -0,0 +1,51 @@ +import { Observable } from 'rxjs'; + +import { RemoteData } from '../../../../core/data/remote-data'; +import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; +import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; +import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; +import { ClaimedTaskSearchResult } from '../../../object-collection/shared/claimed-task-search-result.model'; +import { SearchResultListElementComponent } from '../../search-result-list-element/search-result-list-element.component'; +import { followLink } from '../../../utils/follow-link-config.model'; +import { LinkService } from '../../../../core/cache/builders/link.service'; +import { TruncatableService } from '../../../truncatable/truncatable.service'; + +/** + * This component renders claimed task object for the search result in the list view. + * The task can be claimed, claimed declined or claimed approved. + */ +export abstract class AbstractClaimedSearchResultListElementComponent extends SearchResultListElementComponent { + + /** + * A boolean representing if to show submitter information + */ + public showSubmitter = true; + + /** + * Represent item's status + */ + public status = MyDspaceItemStatusType.VALIDATION; + + /** + * The workflowitem object that belonging to the result object + */ + public workflowitemRD$: Observable>; + + protected constructor( + protected linkService: LinkService, + protected truncatableService: TruncatableService + ) { + super(truncatableService); + } + + /** + * Initialize all instance variables + */ + ngOnInit() { + super.ngOnInit(); + this.linkService.resolveLinks(this.dso, followLink('workflowitem', null, true, + followLink('item'), followLink('submitter') + ), followLink('action')); + this.workflowitemRD$ = this.dso.workflowitem as Observable>; + } +} diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-approved-search-result/claimed-approved-search-result-list-element.component.html b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-approved-search-result/claimed-approved-search-result-list-element.component.html new file mode 100644 index 0000000000..8ebcdbd69a --- /dev/null +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-approved-search-result/claimed-approved-search-result-list-element.component.html @@ -0,0 +1,10 @@ + + + diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-approved-search-result/claimed-approved-search-result-list-element.component.spec.ts b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-approved-search-result/claimed-approved-search-result-list-element.component.spec.ts new file mode 100644 index 0000000000..e56999472e --- /dev/null +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-approved-search-result/claimed-approved-search-result-list-element.component.spec.ts @@ -0,0 +1,101 @@ +import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; + +import { of as observableOf } from 'rxjs'; + +import { Item } from '../../../../../core/shared/item.model'; +import { createSuccessfulRemoteDataObject } from '../../../../remote-data.utils'; +import { WorkflowItem } from '../../../../../core/submission/models/workflowitem.model'; +import { ClaimedTask } from '../../../../../core/tasks/models/claimed-task-object.model'; +import { getMockLinkService } from '../../../../mocks/link-service.mock'; +import { VarDirective } from '../../../../utils/var.directive'; +import { TruncatableService } from '../../../../truncatable/truncatable.service'; +import { LinkService } from '../../../../../core/cache/builders/link.service'; +import { MyDspaceItemStatusType } from '../../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; +import { ClaimedApprovedTaskSearchResult } from '../../../../object-collection/shared/claimed-approved-task-search-result.model'; +import { ClaimedApprovedSearchResultListElementComponent } from './claimed-approved-search-result-list-element.component'; + +let component: ClaimedApprovedSearchResultListElementComponent; +let fixture: ComponentFixture; + +const mockResultObject: ClaimedApprovedTaskSearchResult = new ClaimedApprovedTaskSearchResult(); +mockResultObject.hitHighlights = {}; + +const item = Object.assign(new Item(), { + bundles: observableOf({}), + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'This is just another title' + } + ], + 'dc.type': [ + { + language: null, + value: 'Article' + } + ], + 'dc.contributor.author': [ + { + language: 'en_US', + value: 'Smith, Donald' + } + ], + 'dc.date.issued': [ + { + language: null, + value: '2015-06-26' + } + ] + } +}); +const rdItem = createSuccessfulRemoteDataObject(item); +const workflowitem = Object.assign(new WorkflowItem(), { item: observableOf(rdItem) }); +const rdWorkflowitem = createSuccessfulRemoteDataObject(workflowitem); +mockResultObject.indexableObject = Object.assign(new ClaimedTask(), { workflowitem: observableOf(rdWorkflowitem) }); +const linkService = getMockLinkService(); + +describe('ClaimedApprovedSearchResultListElementComponent', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [NoopAnimationsModule], + declarations: [ClaimedApprovedSearchResultListElementComponent, VarDirective], + providers: [ + { provide: TruncatableService, useValue: {} }, + { provide: LinkService, useValue: linkService } + ], + schemas: [NO_ERRORS_SCHEMA] + }).overrideComponent(ClaimedApprovedSearchResultListElementComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default } + }).compileComponents(); + })); + + beforeEach(async(() => { + fixture = TestBed.createComponent(ClaimedApprovedSearchResultListElementComponent); + component = fixture.componentInstance; + })); + + beforeEach(() => { + component.dso = mockResultObject.indexableObject; + fixture.detectChanges(); + }); + + it('should init workflowitem properly', (done) => { + component.workflowitemRD$.subscribe((workflowitemRD) => { + expect(linkService.resolveLinks).toHaveBeenCalledWith( + component.dso, + jasmine.objectContaining({ name: 'workflowitem' }), + jasmine.objectContaining({ name: 'action' }) + ); + expect(workflowitemRD.payload).toEqual(workflowitem); + done(); + }); + }); + + it('should have properly status', () => { + expect(component.status).toEqual(MyDspaceItemStatusType.APPROVED); + }); + +}); diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-approved-search-result/claimed-approved-search-result-list-element.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-approved-search-result/claimed-approved-search-result-list-element.component.ts new file mode 100644 index 0000000000..f640a9e36e --- /dev/null +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-approved-search-result/claimed-approved-search-result-list-element.component.ts @@ -0,0 +1,36 @@ +import { Component } from '@angular/core'; +import { LocationStrategy, PathLocationStrategy } from '@angular/common'; +import { ViewMode } from '../../../../../core/shared/view-mode.model'; +import { ClaimedApprovedTaskSearchResult } from '../../../../object-collection/shared/claimed-approved-task-search-result.model'; +import { listableObjectComponent } from '../../../../object-collection/shared/listable-object/listable-object.decorator'; +import { AbstractClaimedSearchResultListElementComponent } from '../abstract-claimed-search-result-list-element.component'; +import { LinkService } from '../../../../../core/cache/builders/link.service'; +import { TruncatableService } from '../../../../truncatable/truncatable.service'; +import { MyDspaceItemStatusType } from '../../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; + +/** + * This component renders claimed task approved object for the search result in the list view. + */ +@Component({ + selector: 'ds-claimed-approved-search-result-list-element', + styleUrls: ['../../../search-result-list-element/search-result-list-element.component.scss'], + templateUrl: './claimed-approved-search-result-list-element.component.html', + providers: [Location, { provide: LocationStrategy, useClass: PathLocationStrategy }] +}) + +@listableObjectComponent(ClaimedApprovedTaskSearchResult, ViewMode.ListElement) +export class ClaimedApprovedSearchResultListElementComponent extends AbstractClaimedSearchResultListElementComponent { + + /** + * Represent item's status + */ + public status = MyDspaceItemStatusType.APPROVED; + + constructor( + protected linkService: LinkService, + protected truncatableService: TruncatableService + ) { + super(linkService, truncatableService); + } + +} diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-search-result/claimed-declided-search-result-list-element.component.spec.ts b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-search-result/claimed-declided-search-result-list-element.component.spec.ts new file mode 100644 index 0000000000..8a8d542063 --- /dev/null +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-search-result/claimed-declided-search-result-list-element.component.spec.ts @@ -0,0 +1,101 @@ +import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; + +import { of as observableOf } from 'rxjs'; + +import { ClaimedDeclinedSearchResultListElementComponent } from './claimed-declined-search-result-list-element.component'; +import { ClaimedDeclinedTaskSearchResult } from '../../../../object-collection/shared/claimed-declined-task-search-result.model'; +import { Item } from '../../../../../core/shared/item.model'; +import { createSuccessfulRemoteDataObject } from '../../../../remote-data.utils'; +import { WorkflowItem } from '../../../../../core/submission/models/workflowitem.model'; +import { ClaimedTask } from '../../../../../core/tasks/models/claimed-task-object.model'; +import { getMockLinkService } from '../../../../mocks/link-service.mock'; +import { VarDirective } from '../../../../utils/var.directive'; +import { TruncatableService } from '../../../../truncatable/truncatable.service'; +import { LinkService } from '../../../../../core/cache/builders/link.service'; +import { MyDspaceItemStatusType } from '../../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; + +let component: ClaimedDeclinedSearchResultListElementComponent; +let fixture: ComponentFixture; + +const mockResultObject: ClaimedDeclinedTaskSearchResult = new ClaimedDeclinedTaskSearchResult(); +mockResultObject.hitHighlights = {}; + +const item = Object.assign(new Item(), { + bundles: observableOf({}), + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'This is just another title' + } + ], + 'dc.type': [ + { + language: null, + value: 'Article' + } + ], + 'dc.contributor.author': [ + { + language: 'en_US', + value: 'Smith, Donald' + } + ], + 'dc.date.issued': [ + { + language: null, + value: '2015-06-26' + } + ] + } +}); +const rdItem = createSuccessfulRemoteDataObject(item); +const workflowitem = Object.assign(new WorkflowItem(), { item: observableOf(rdItem) }); +const rdWorkflowitem = createSuccessfulRemoteDataObject(workflowitem); +mockResultObject.indexableObject = Object.assign(new ClaimedTask(), { workflowitem: observableOf(rdWorkflowitem) }); +const linkService = getMockLinkService(); + +describe('ClaimedDeclinedSearchResultListElementComponent', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [NoopAnimationsModule], + declarations: [ClaimedDeclinedSearchResultListElementComponent, VarDirective], + providers: [ + { provide: TruncatableService, useValue: {} }, + { provide: LinkService, useValue: linkService } + ], + schemas: [NO_ERRORS_SCHEMA] + }).overrideComponent(ClaimedDeclinedSearchResultListElementComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default } + }).compileComponents(); + })); + + beforeEach(async(() => { + fixture = TestBed.createComponent(ClaimedDeclinedSearchResultListElementComponent); + component = fixture.componentInstance; + })); + + beforeEach(() => { + component.dso = mockResultObject.indexableObject; + fixture.detectChanges(); + }); + + it('should init workflowitem properly', (done) => { + component.workflowitemRD$.subscribe((workflowitemRD) => { + expect(linkService.resolveLinks).toHaveBeenCalledWith( + component.dso, + jasmine.objectContaining({ name: 'workflowitem' }), + jasmine.objectContaining({ name: 'action' }) + ); + expect(workflowitemRD.payload).toEqual(workflowitem); + done(); + }); + }); + + it('should have properly status', () => { + expect(component.status).toEqual(MyDspaceItemStatusType.DECLINED); + }); + +}); diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-search-result/claimed-declined-search-result-list-element.component.html b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-search-result/claimed-declined-search-result-list-element.component.html new file mode 100644 index 0000000000..f20696823c --- /dev/null +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-search-result/claimed-declined-search-result-list-element.component.html @@ -0,0 +1,10 @@ + + + diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-search-result/claimed-declined-search-result-list-element.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-search-result/claimed-declined-search-result-list-element.component.ts new file mode 100644 index 0000000000..14884ebd3d --- /dev/null +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-search-result/claimed-declined-search-result-list-element.component.ts @@ -0,0 +1,37 @@ +import { Component } from '@angular/core'; +import { Location, LocationStrategy, PathLocationStrategy } from '@angular/common'; + +import { AbstractClaimedSearchResultListElementComponent } from '../abstract-claimed-search-result-list-element.component'; +import { listableObjectComponent } from '../../../../object-collection/shared/listable-object/listable-object.decorator'; +import { ClaimedDeclinedTaskSearchResult } from '../../../../object-collection/shared/claimed-declined-task-search-result.model'; +import { ViewMode } from '../../../../../core/shared/view-mode.model'; +import { LinkService } from '../../../../../core/cache/builders/link.service'; +import { TruncatableService } from '../../../../truncatable/truncatable.service'; +import { MyDspaceItemStatusType } from '../../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; + +/** + * This component renders claimed task declined object for the search result in the list view. + */ +@Component({ + selector: 'ds-claimed-declined-search-result-list-element', + styleUrls: ['../../../search-result-list-element/search-result-list-element.component.scss'], + templateUrl: './claimed-declined-search-result-list-element.component.html', + providers: [Location, { provide: LocationStrategy, useClass: PathLocationStrategy }] +}) + +@listableObjectComponent(ClaimedDeclinedTaskSearchResult, ViewMode.ListElement) +export class ClaimedDeclinedSearchResultListElementComponent extends AbstractClaimedSearchResultListElementComponent { + + /** + * Represent item's status + */ + public status = MyDspaceItemStatusType.DECLINED; + + constructor( + protected linkService: LinkService, + protected truncatableService: TruncatableService + ) { + super(linkService, truncatableService); + } + +} diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.html b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.html index b35a4f8741..30aac357a4 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.html +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.html @@ -4,7 +4,6 @@ [object]="object" [showSubmitter]="showSubmitter" [status]="status"> - - + diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.spec.ts b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.spec.ts index f06f3eac81..fc00775130 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.spec.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { of as observableOf } from 'rxjs'; @@ -15,12 +15,11 @@ import { TruncatableService } from '../../../truncatable/truncatable.service'; import { VarDirective } from '../../../utils/var.directive'; import { LinkService } from '../../../../core/cache/builders/link.service'; import { getMockLinkService } from '../../../mocks/link-service.mock'; +import { By } from '@angular/platform-browser'; let component: ClaimedSearchResultListElementComponent; let fixture: ComponentFixture; -const compIndex = 1; - const mockResultObject: ClaimedTaskSearchResult = new ClaimedTaskSearchResult(); mockResultObject.hitHighlights = {}; @@ -99,4 +98,16 @@ describe('ClaimedSearchResultListElementComponent', () => { it('should have properly status', () => { expect(component.status).toEqual(MyDspaceItemStatusType.VALIDATION); }); + + it('should forward claimed-task-actions processComplete event to reloadObject event emitter', fakeAsync(() => { + spyOn(component.reloadedObject, 'emit').and.callThrough(); + const actionPayload: any = { reloadedObject: {}}; + + const actionsComponent = fixture.debugElement.query(By.css('ds-claimed-task-actions')); + actionsComponent.triggerEventHandler('processCompleted', actionPayload) + tick(); + + expect(component.reloadedObject.emit).toHaveBeenCalledWith(actionPayload.reloadedObject); + + })); }); diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts index d149595514..7c1033ba53 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts @@ -1,63 +1,27 @@ import { Component } from '@angular/core'; import { Location, LocationStrategy, PathLocationStrategy } from '@angular/common'; -import { Observable } from 'rxjs'; - import { ViewMode } from '../../../../core/shared/view-mode.model'; -import { RemoteData } from '../../../../core/data/remote-data'; -import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; -import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; -import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; import { listableObjectComponent } from '../../../object-collection/shared/listable-object/listable-object.decorator'; import { ClaimedTaskSearchResult } from '../../../object-collection/shared/claimed-task-search-result.model'; -import { SearchResultListElementComponent } from '../../search-result-list-element/search-result-list-element.component'; -import { followLink } from '../../../utils/follow-link-config.model'; import { LinkService } from '../../../../core/cache/builders/link.service'; import { TruncatableService } from '../../../truncatable/truncatable.service'; +import { AbstractClaimedSearchResultListElementComponent } from './abstract-claimed-search-result-list-element.component'; -/** - * This component renders claimed task object for the search result in the list view. - */ @Component({ selector: 'ds-claimed-search-result-list-element', styleUrls: ['../../search-result-list-element/search-result-list-element.component.scss'], templateUrl: './claimed-search-result-list-element.component.html', providers: [Location, { provide: LocationStrategy, useClass: PathLocationStrategy }] }) - @listableObjectComponent(ClaimedTaskSearchResult, ViewMode.ListElement) -export class ClaimedSearchResultListElementComponent extends SearchResultListElementComponent { - - /** - * A boolean representing if to show submitter information - */ - public showSubmitter = true; - - /** - * Represent item's status - */ - public status = MyDspaceItemStatusType.VALIDATION; - - /** - * The workflowitem object that belonging to the result object - */ - public workflowitemRD$: Observable>; +export class ClaimedSearchResultListElementComponent extends AbstractClaimedSearchResultListElementComponent { constructor( protected linkService: LinkService, protected truncatableService: TruncatableService ) { - super(truncatableService); + super(linkService, truncatableService); } - /** - * Initialize all instance variables - */ - ngOnInit() { - super.ngOnInit(); - this.linkService.resolveLinks(this.dso, followLink('workflowitem', null, true, - followLink('item'), followLink('submitter') - ), followLink('action')); - this.workflowitemRD$ = this.dso.workflowitem as Observable>; - } } diff --git a/src/app/shared/object-list/my-dspace-result-list-element/item-search-result/item-search-result-list-element-submission.component.html b/src/app/shared/object-list/my-dspace-result-list-element/item-search-result/item-search-result-list-element-submission.component.html index e1b1435481..e422a84641 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/item-search-result/item-search-result-list-element-submission.component.html +++ b/src/app/shared/object-list/my-dspace-result-list-element/item-search-result/item-search-result-list-element-submission.component.html @@ -2,4 +2,4 @@ [object]="object" [status]="status"> - + diff --git a/src/app/shared/object-list/my-dspace-result-list-element/item-search-result/item-search-result-list-element-submission.component.spec.ts b/src/app/shared/object-list/my-dspace-result-list-element/item-search-result/item-search-result-list-element-submission.component.spec.ts index 45af23320f..0149e0a807 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/item-search-result/item-search-result-list-element-submission.component.spec.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/item-search-result/item-search-result-list-element-submission.component.spec.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { of as observableOf } from 'rxjs'; @@ -9,12 +9,11 @@ import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspa import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model'; import { ItemSearchResultListElementSubmissionComponent } from './item-search-result-list-element-submission.component'; import { TruncatableService } from '../../../truncatable/truncatable.service'; +import { By } from '@angular/platform-browser'; let component: ItemSearchResultListElementSubmissionComponent; let fixture: ComponentFixture; -const compIndex = 1; - const mockResultObject: ItemSearchResult = new ItemSearchResult(); mockResultObject.hitHighlights = {}; @@ -75,4 +74,16 @@ describe('ItemMyDSpaceResultListElementComponent', () => { it('should have properly status', () => { expect(component.status).toEqual(MyDspaceItemStatusType.ARCHIVED); }); + + it('should forward item-actions processComplete event to reloadObject event emitter', fakeAsync(() => { + spyOn(component.reloadedObject, 'emit').and.callThrough(); + const actionPayload: any = { reloadedObject: {}}; + + const actionsComponent = fixture.debugElement.query(By.css('ds-item-actions')); + actionsComponent.triggerEventHandler('processCompleted', actionPayload) + tick(); + + expect(component.reloadedObject.emit).toHaveBeenCalledWith(actionPayload.reloadedObject); + + })); }); diff --git a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.html b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.html index 9358e35bed..25e2c4f8c4 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.html +++ b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.html @@ -4,6 +4,5 @@ [object]="object" [showSubmitter]="showSubmitter" [status]="status"> - - + diff --git a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.spec.ts b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.spec.ts index fbeb63c667..7f4e50798e 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.spec.ts @@ -1,5 +1,5 @@ -import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import {ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA} from '@angular/core'; +import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { of as observableOf } from 'rxjs'; @@ -15,12 +15,11 @@ import { TruncatableService } from '../../../truncatable/truncatable.service'; import { VarDirective } from '../../../utils/var.directive'; import { LinkService } from '../../../../core/cache/builders/link.service'; import { getMockLinkService } from '../../../mocks/link-service.mock'; +import { By } from '@angular/platform-browser'; let component: PoolSearchResultListElementComponent; let fixture: ComponentFixture; -const compIndex = 1; - const mockResultObject: PoolTaskSearchResult = new PoolTaskSearchResult(); mockResultObject.hitHighlights = {}; @@ -99,4 +98,15 @@ describe('PoolSearchResultListElementComponent', () => { it('should have properly status', () => { expect(component.status).toEqual(MyDspaceItemStatusType.WAITING_CONTROLLER); }); + + it('should forward pool-task-actions processCompleted event to the reloadedObject event emitter', fakeAsync(() => { + spyOn(component.reloadedObject, 'emit').and.callThrough(); + const actionPayload: any = { reloadedObject: {}}; + const actionsComponents = fixture.debugElement.query(By.css('ds-pool-task-actions')); + actionsComponents.triggerEventHandler('processCompleted', actionPayload) + tick(); + + expect(component.reloadedObject.emit).toHaveBeenCalledWith(actionPayload.reloadedObject); + + })); }); diff --git a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts index 0953af3c76..14e547c173 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts @@ -63,4 +63,5 @@ export class PoolSearchResultListElementComponent extends SearchResultListElemen ), followLink('action')); this.workflowitemRD$ = this.dso.workflowitem as Observable>; } + } diff --git a/src/app/shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component.html b/src/app/shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component.html index ced2846b4b..74fc5fd06d 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component.html +++ b/src/app/shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component.html @@ -4,7 +4,7 @@ [object]="object" [status]="status"> - + ; -const compIndex = 1; - const mockResultObject: WorkflowItemSearchResult = new WorkflowItemSearchResult(); mockResultObject.hitHighlights = {}; @@ -96,4 +95,16 @@ describe('WorkflowItemSearchResultListElementComponent', () => { it('should have properly status', () => { expect(component.status).toEqual(MyDspaceItemStatusType.WORKFLOW); }); + + it('should forward workflowitem-actions processCompleted event to the reloadedObject event emitter', fakeAsync(() => { + spyOn(component.reloadedObject, 'emit').and.callThrough(); + const actionPayload: any = { reloadedObject: {}}; + + const actionsComponent = fixture.debugElement.query(By.css('ds-workflowitem-actions')); + actionsComponent.triggerEventHandler('processCompleted', actionPayload) + tick(); + + expect(component.reloadedObject.emit).toHaveBeenCalledWith(actionPayload.reloadedObject); + + })); }); diff --git a/src/app/shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component.html b/src/app/shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component.html index 8966b4b1d8..41d95b87af 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component.html +++ b/src/app/shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component.html @@ -4,7 +4,7 @@ [object]="object" [status]="status"> - + ; -const compIndex = 1; - const mockResultObject: WorkflowItemSearchResult = new WorkflowItemSearchResult(); mockResultObject.hitHighlights = {}; @@ -95,4 +94,17 @@ describe('WorkspaceItemSearchResultListElementComponent', () => { it('should have properly status', () => { expect(component.status).toEqual(MyDspaceItemStatusType.WORKSPACE); }); + + it('should forward workspaceitem-actions processCompleted event to the reloadedObject event emitter', fakeAsync(() => { + + spyOn(component.reloadedObject, 'emit').and.callThrough(); + const actionPayload: any = { reloadedObject: {}}; + + const actionsComponent = fixture.debugElement.query(By.css('ds-workspaceitem-actions')); + actionsComponent.triggerEventHandler('processCompleted', actionPayload) + tick(); + + expect(component.reloadedObject.emit).toHaveBeenCalledWith(actionPayload.reloadedObject); + + })); }); diff --git a/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts b/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts index 54e8c9de01..93e87d3db7 100644 --- a/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts +++ b/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject, OnInit } from '@angular/core'; +import { Component, EventEmitter, OnInit, Output } from '@angular/core'; import { Observable } from 'rxjs'; import { SearchResult } from '../../search/search-result.model'; @@ -12,7 +12,6 @@ import { Metadata } from '../../../core/shared/metadata.utils'; selector: 'ds-search-result-list-element', template: `` }) - export class SearchResultListElementComponent, K extends DSpaceObject> extends AbstractListableElementComponent implements OnInit { /** * The DSpaceObject of the search result diff --git a/src/environments/environment.common.ts b/src/environments/environment.common.ts index eb961a38eb..f107e79c97 100644 --- a/src/environments/environment.common.ts +++ b/src/environments/environment.common.ts @@ -22,9 +22,9 @@ export const environment: GlobalConfig = { // The REST API server settings. // NOTE: these must be "synced" with the 'dspace.server.url' setting in your backend's local.cfg. rest: { - ssl: true, - host: 'dspace7.4science.cloud', - port: 443, + ssl: false, + host: 'localhost', + port: 8080, // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript nameSpace: '/server', },