From e32c86f1279f6279ef17e1a88529729c35604426 Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Fri, 18 Dec 2020 12:19:09 +0100 Subject: [PATCH 01/18] [CSTPER-3620] Workflow Actions refresh entire MyDSpace page instead of just WorkflowItem Task Service implementation. ReloadableAction abstraction. --- .../+my-dspace-page/my-dspace-page.module.ts | 6 + .../tasks/claimed-task-data.service.spec.ts | 43 ++++ .../core/tasks/claimed-task-data.service.ts | 38 ++- .../core/tasks/pool-task-data.service.spec.ts | 39 +++- src/app/core/tasks/pool-task-data.service.ts | 37 ++- src/app/core/tasks/tasks.service.spec.ts | 40 +++- src/app/core/tasks/tasks.service.ts | 49 +++- ...claimed-task-actions-abstract.component.ts | 82 ++++--- ...med-task-actions-approve.component.spec.ts | 56 ++++- .../claimed-task-actions-approve.component.ts | 39 +++- .../claimed-task-actions.component.html | 4 +- ...-task-actions-edit-metadata.component.html | 2 +- ...sk-actions-edit-metadata.component.spec.ts | 28 ++- ...ed-task-actions-edit-metadata.component.ts | 17 +- ...imed-task-actions-reject.component.spec.ts | 65 +++++- .../claimed-task-actions-reject.component.ts | 61 +++-- ...k-actions-return-to-pool.component.spec.ts | 55 ++++- ...d-task-actions-return-to-pool.component.ts | 40 ++-- .../claimed-task-actions-loader.component.ts | 5 +- .../mydspace-actions/mydspace-actions.ts | 22 +- .../mydspace-reloadable-actions.ts | 137 +++++++++++ .../pool-task-actions.component.html | 6 +- .../pool-task-actions.component.spec.ts | 216 ++++++++++++++---- .../pool-task/pool-task-actions.component.ts | 65 ++++-- ...aimed-approved-task-search-result.model.ts | 8 + ...aimed-declined-task-search-result.model.ts | 8 + ...ble-object-component-loader.component.scss | 3 + ...-object-component-loader.component.spec.ts | 19 +- ...table-object-component-loader.component.ts | 41 +++- .../my-dspace-item-status-type.ts | 4 +- .../abstract-listable-element.component.ts | 9 +- ...earch-result-detail-element.component.html | 2 +- ...ch-result-detail-element.component.spec.ts | 15 +- ...earch-result-detail-element.component.html | 2 +- ...ch-result-detail-element.component.spec.ts | 14 +- ...ed-search-result-list-element.component.ts | 51 +++++ ...-search-result-list-element.component.html | 10 + ...arch-result-list-element.component.spec.ts | 101 ++++++++ ...ed-search-result-list-element.component.ts | 36 +++ ...arch-result-list-element.component.spec.ts | 101 ++++++++ ...-search-result-list-element.component.html | 10 + ...ed-search-result-list-element.component.ts | 37 +++ ...-search-result-list-element.component.html | 3 +- ...arch-result-list-element.component.spec.ts | 17 +- ...ed-search-result-list-element.component.ts | 42 +--- ...ult-list-element-submission.component.html | 2 +- ...-list-element-submission.component.spec.ts | 17 +- ...-search-result-list-element.component.html | 3 +- ...arch-result-list-element.component.spec.ts | 18 +- ...ol-search-result-list-element.component.ts | 1 + ...-search-result-list-element.component.html | 2 +- ...arch-result-list-element.component.spec.ts | 17 +- ...-search-result-list-element.component.html | 2 +- ...arch-result-list-element.component.spec.ts | 20 +- .../search-result-list-element.component.ts | 3 +- src/environments/environment.common.ts | 6 +- 56 files changed, 1489 insertions(+), 287 deletions(-) create mode 100644 src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts create mode 100644 src/app/shared/object-collection/shared/claimed-approved-task-search-result.model.ts create mode 100644 src/app/shared/object-collection/shared/claimed-declined-task-search-result.model.ts create mode 100644 src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.scss create mode 100644 src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/abstract-claimed-search-result-list-element.component.ts create mode 100644 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 create mode 100644 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 create mode 100644 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 create mode 100644 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 create mode 100644 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 create mode 100644 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 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', }, From 8013260f78024be04d1207c6df67bbda59d394e7 Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Tue, 22 Dec 2020 13:28:44 +0100 Subject: [PATCH 02/18] [CSTPER-3620] Workflow Actions refresh entire MyDSpace page instead of just WorkflowItem Task Service implementation. ReloadableAction abstraction. --- ...claimed-task-actions-abstract.component.ts | 5 +- ...imed-task-actions-loader.component.spec.ts | 21 +- .../mydspace-reloadable-actions.spec.ts | 260 ++++++++++++++++++ .../mydspace-reloadable-actions.ts | 10 +- .../pool-task-actions.component.spec.ts | 153 +---------- 5 files changed, 294 insertions(+), 155 deletions(-) create mode 100644 src/app/shared/mydspace-actions/mydspace-reloadable-actions.spec.ts 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 9e75cf1f5e..eb43a33a20 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 @@ -79,7 +79,10 @@ export abstract class ClaimedTaskActionsAbstractComponent extends MyDSpaceReload * Retrieve the itemUuid. */ initReloadAnchor() { - (this.object as any).workflowitem.pipe( + if (!(this.object as any).workflowitem) { + return; + } + this.object.workflowitem.pipe( getFirstSucceededRemoteDataPayload(), switchMap((workflowItem: WorkflowItem) => workflowItem.item.pipe(getFirstSucceededRemoteDataPayload()) )) diff --git a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts index 036b08634a..fc37cf4fea 100644 --- a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts +++ b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts @@ -1,6 +1,6 @@ import { ClaimedTaskActionsLoaderComponent } from './claimed-task-actions-loader.component'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { ChangeDetectionStrategy, ComponentFactoryResolver, NO_ERRORS_SCHEMA } from '@angular/core'; +import { ChangeDetectionStrategy, ComponentFactoryResolver, Injector, NO_ERRORS_SCHEMA } from '@angular/core'; import * as decorators from './claimed-task-actions-decorator'; import { ClaimedTaskActionsDirective } from './claimed-task-actions.directive'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; @@ -8,6 +8,19 @@ import { TranslateModule } from '@ngx-translate/core'; import { ClaimedTaskActionsEditMetadataComponent } from '../edit-metadata/claimed-task-actions-edit-metadata.component'; import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service'; import { spyOnExported } from '../../../testing/utils.test'; +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'; + +const searchService = getMockSearchService(); + +const requestService = getMockRequestService(); describe('ClaimedTaskActionsLoaderComponent', () => { let comp: ClaimedTaskActionsLoaderComponent; @@ -23,6 +36,12 @@ describe('ClaimedTaskActionsLoaderComponent', () => { schemas: [NO_ERRORS_SCHEMA], providers: [ { 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: {} }, ComponentFactoryResolver ] }).overrideComponent(ClaimedTaskActionsLoaderComponent, { diff --git a/src/app/shared/mydspace-actions/mydspace-reloadable-actions.spec.ts b/src/app/shared/mydspace-actions/mydspace-reloadable-actions.spec.ts new file mode 100644 index 0000000000..609dad39d2 --- /dev/null +++ b/src/app/shared/mydspace-actions/mydspace-reloadable-actions.spec.ts @@ -0,0 +1,260 @@ +import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { Router } from '@angular/router'; +import { of as observableOf } from 'rxjs'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { PoolTaskDataService } from '../../core/tasks/pool-task-data.service'; +import { ClaimedTaskDataService } from '../../core/tasks/claimed-task-data.service'; +import { PoolTaskActionsComponent } from './pool-task/pool-task-actions.component'; +import { PoolTask } from '../../core/tasks/models/pool-task-object.model'; +import { NotificationsServiceStub } from '../testing/notifications-service.stub'; +import { RouterStub } from '../testing/router.stub'; +import { getMockSearchService } from '../mocks/search-service.mock'; +import { getMockRequestService } from '../mocks/request.service.mock'; +import { Item } from '../../core/shared/item.model'; +import { createFailedRemoteDataObject, createSuccessfulRemoteDataObject } from '../remote-data.utils'; +import { WorkflowItem } from '../../core/submission/models/workflowitem.model'; +import { TranslateLoaderMock } from '../mocks/translate-loader.mock'; +import { NotificationsService } from '../notifications/notifications.service'; +import { SearchService } from '../../core/shared/search/search.service'; +import { RequestService } from '../../core/data/request.service'; +import { ProcessTaskResponse } from '../../core/tasks/models/process-task-response'; + +let mockDataService: PoolTaskDataService; +let mockClaimedTaskDataService: ClaimedTaskDataService; + +let component: PoolTaskActionsComponent; +let fixture: ComponentFixture; + +let mockObject: PoolTask; +let notificationsServiceStub: NotificationsServiceStub; +let router: RouterStub; + +const searchService = getMockSearchService(); + +const requestService = getMockRequestService(); + +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); +mockObject = Object.assign(new PoolTask(), { workflowitem: observableOf(rdWorkflowitem), id: '1234' }); + +describe('MyDSpaceReloadableActionsComponent', () => { + 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({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }) + ], + declarations: [PoolTaskActionsComponent], + providers: [ + { provide: Injector, useValue: {} }, + { 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: requestService } + ], + schemas: [NO_ERRORS_SCHEMA] + }).overrideComponent(PoolTaskActionsComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default } + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PoolTaskActionsComponent); + component = fixture.componentInstance; + component.object = mockObject; + notificationsServiceStub = TestBed.get(NotificationsService); + router = TestBed.get(Router); + fixture.detectChanges(); + }); + + afterEach(() => { + fixture = null; + component = null; + }); + + describe('on reload action init', () => { + + beforeEach(() => { + spyOn(component, 'initReloadAnchor').and.returnValue(null); + spyOn(component, 'initObjects'); + }); + + it('should call initReloadAnchor and initObjects on init', async(() => { + component.ngOnInit(); + + fixture.detectChanges(); + + 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 show error notification', (done) => { + + 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/mydspace-reloadable-actions.ts b/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts index 4fe3afd833..14928f156c 100644 --- a/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts +++ b/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts @@ -15,7 +15,7 @@ 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 { getFirstCompletedRemoteData } from '../../core/shared/operators'; import { getSearchResultFor } from '../search/search-result-element-decorator'; import { MyDSpaceActionsComponent } from './mydspace-actions'; @@ -128,9 +128,13 @@ export abstract class MyDSpaceReloadableActionsComponent { return this.reloadObjectExecution().pipe( switchMap((res) => { - return res instanceof RemoteData ? of(res).pipe(getFirstSucceededRemoteDataPayload()) : of(res) + if (res instanceof RemoteData) { + return of(res).pipe(getFirstCompletedRemoteData(), map((completed) => completed.payload)) + } else { + return of(res); + } })).pipe(map((dso) => { - return this.convertReloadedObject(dso); + return dso ? this.convertReloadedObject(dso) : dso; })) } 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 3b4eb23430..e8777d7a27 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 @@ -16,7 +16,7 @@ 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 { createFailedRemoteDataObject, createSuccessfulRemoteDataObject } from '../../remote-data.utils'; +import { 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'; @@ -37,7 +37,7 @@ let router: RouterStub; const searchService = getMockSearchService(); -const requestServce = getMockRequestService(); +const requestService = getMockRequestService(); const item = Object.assign(new Item(), { bundles: observableOf({}), @@ -94,7 +94,7 @@ describe('PoolTaskActionsComponent', () => { { provide: PoolTaskDataService, useValue: mockDataService }, { provide: ClaimedTaskDataService, useValue: mockClaimedTaskDataService }, { provide: SearchService, useValue: searchService }, - { provide: RequestService, useValue: requestServce } + { provide: RequestService, useValue: requestService } ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(PoolTaskActionsComponent, { @@ -164,151 +164,4 @@ describe('PoolTaskActionsComponent', () => { })); - // general reload assertion - describe('on reload action init', () => { - - beforeEach(() => { - spyOn(component, 'initReloadAnchor').and.returnValue(null); - spyOn(component, 'initObjects'); - }); - - it('should call initReloadAnchor and initObjects on init', async(() => { - component.ngOnInit(); - - fixture.detectChanges(); - - 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 show error notification', (done) => { - - 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(); - }) - }); - - }); - }); From 704d67da79fd7639a9097b56b85a36410e82ada1 Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Tue, 22 Dec 2020 15:53:43 +0100 Subject: [PATCH 03/18] [CSTPER-3620] Workflow Actions refresh entire MyDSpace page instead of just WorkflowItem Removed abstraction on claimed-search-result-list-element components --- ...ed-search-result-list-element.component.ts | 51 ------------------- ...ed-search-result-list-element.component.ts | 38 +++++++++++--- ...ed-search-result-list-element.component.ts | 38 +++++++++++--- ...ed-search-result-list-element.component.ts | 40 +++++++++++++-- .../search-result-list-element.component.ts | 2 +- 5 files changed, 101 insertions(+), 68 deletions(-) delete mode 100644 src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/abstract-claimed-search-result-list-element.component.ts 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 deleted file mode 100644 index 3dab2f844f..0000000000 --- a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/abstract-claimed-search-result-list-element.component.ts +++ /dev/null @@ -1,51 +0,0 @@ -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.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 index f640a9e36e..70b576086d 100644 --- 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 @@ -3,10 +3,16 @@ 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'; +import { Observable } from 'rxjs'; +import { RemoteData } from '../../../../../core/data/remote-data'; +import { WorkflowItem } from '../../../../../core/submission/models/workflowitem.model'; +import { followLink } from '../../../../utils/follow-link-config.model'; +import { SearchResultListElementComponent } from '../../../search-result-list-element/search-result-list-element.component'; +import { ClaimedTaskSearchResult} from '../../../../object-collection/shared/claimed-task-search-result.model'; +import { ClaimedTask } from '../../../../../core/tasks/models/claimed-task-object.model'; /** * This component renders claimed task approved object for the search result in the list view. @@ -17,20 +23,40 @@ import { MyDspaceItemStatusType } from '../../../../object-collection/shared/myd templateUrl: './claimed-approved-search-result-list-element.component.html', providers: [Location, { provide: LocationStrategy, useClass: PathLocationStrategy }] }) - @listableObjectComponent(ClaimedApprovedTaskSearchResult, ViewMode.ListElement) -export class ClaimedApprovedSearchResultListElementComponent extends AbstractClaimedSearchResultListElementComponent { +export class ClaimedApprovedSearchResultListElementComponent extends SearchResultListElementComponent { + + /** + * A boolean representing if to show submitter information + */ + public showSubmitter = true; /** * Represent item's status */ - public status = MyDspaceItemStatusType.APPROVED; + public status = MyDspaceItemStatusType.VALIDATION; - constructor( + /** + * The workflowitem object that belonging to the result object + */ + public workflowitemRD$: Observable>; + + public constructor( protected linkService: LinkService, protected truncatableService: TruncatableService ) { - super(linkService, 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-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 index 14884ebd3d..bfa29b04e1 100644 --- 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 @@ -1,13 +1,19 @@ 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'; +import { Observable } from 'rxjs'; +import { RemoteData } from '../../../../../core/data/remote-data'; +import { WorkflowItem } from '../../../../../core/submission/models/workflowitem.model'; +import { followLink } from '../../../../utils/follow-link-config.model'; +import { SearchResultListElementComponent } from '../../../search-result-list-element/search-result-list-element.component'; +import { ClaimedTaskSearchResult } from '../../../../object-collection/shared/claimed-task-search-result.model'; +import { ClaimedTask } from '../../../../../core/tasks/models/claimed-task-object.model'; /** * This component renders claimed task declined object for the search result in the list view. @@ -18,20 +24,40 @@ import { MyDspaceItemStatusType } from '../../../../object-collection/shared/myd templateUrl: './claimed-declined-search-result-list-element.component.html', providers: [Location, { provide: LocationStrategy, useClass: PathLocationStrategy }] }) - @listableObjectComponent(ClaimedDeclinedTaskSearchResult, ViewMode.ListElement) -export class ClaimedDeclinedSearchResultListElementComponent extends AbstractClaimedSearchResultListElementComponent { +export class ClaimedDeclinedSearchResultListElementComponent extends SearchResultListElementComponent { + + /** + * A boolean representing if to show submitter information + */ + public showSubmitter = true; /** * Represent item's status */ - public status = MyDspaceItemStatusType.DECLINED; + public status = MyDspaceItemStatusType.VALIDATION; - constructor( + /** + * The workflowitem object that belonging to the result object + */ + public workflowitemRD$: Observable>; + + public constructor( protected linkService: LinkService, protected truncatableService: TruncatableService ) { - super(linkService, 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-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 7c1033ba53..651ed35dc1 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 @@ -6,7 +6,13 @@ import { listableObjectComponent } from '../../../object-collection/shared/lista import { ClaimedTaskSearchResult } from '../../../object-collection/shared/claimed-task-search-result.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'; +import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; +import { Observable } from 'rxjs'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; +import { followLink } from '../../../utils/follow-link-config.model'; +import { SearchResultListElementComponent } from '../../search-result-list-element/search-result-list-element.component'; +import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; @Component({ selector: 'ds-claimed-search-result-list-element', @@ -15,13 +21,39 @@ import { AbstractClaimedSearchResultListElementComponent } from './abstract-clai providers: [Location, { provide: LocationStrategy, useClass: PathLocationStrategy }] }) @listableObjectComponent(ClaimedTaskSearchResult, ViewMode.ListElement) -export class ClaimedSearchResultListElementComponent extends AbstractClaimedSearchResultListElementComponent { +export class ClaimedSearchResultListElementComponent extends SearchResultListElementComponent { - constructor( + /** + * 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>; + + public constructor( protected linkService: LinkService, protected truncatableService: TruncatableService ) { - super(linkService, 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/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 93e87d3db7..3ad4f5e7e6 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, EventEmitter, OnInit, Output } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { SearchResult } from '../../search/search-result.model'; From e41e7dfa074a4353cc2b2487c0ce9f4e63a161bc Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Tue, 22 Dec 2020 16:25:26 +0100 Subject: [PATCH 04/18] [CSTPER-3620] Workflow Actions refresh entire MyDSpace page instead of just WorkflowItem Cleanup --- ...claimed-approved-search-result-list-element.component.ts | 6 ++---- ...claimed-declined-search-result-list-element.component.ts | 6 ++---- .../claimed-search-result-list-element.component.ts | 4 +--- 3 files changed, 5 insertions(+), 11 deletions(-) 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 index 70b576086d..9eb538ab6a 100644 --- 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 @@ -1,5 +1,4 @@ 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'; @@ -20,8 +19,7 @@ import { ClaimedTask } from '../../../../../core/tasks/models/claimed-task-objec @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 }] + templateUrl: './claimed-approved-search-result-list-element.component.html' }) @listableObjectComponent(ClaimedApprovedTaskSearchResult, ViewMode.ListElement) export class ClaimedApprovedSearchResultListElementComponent extends SearchResultListElementComponent { @@ -34,7 +32,7 @@ export class ClaimedApprovedSearchResultListElementComponent extends SearchResul /** * Represent item's status */ - public status = MyDspaceItemStatusType.VALIDATION; + public status = MyDspaceItemStatusType.APPROVED; /** * The workflowitem object that belonging to the result object 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 index bfa29b04e1..ee90953c8c 100644 --- 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 @@ -1,5 +1,4 @@ import { Component } from '@angular/core'; -import { Location, LocationStrategy, PathLocationStrategy } from '@angular/common'; import { listableObjectComponent } from '../../../../object-collection/shared/listable-object/listable-object.decorator'; import { ClaimedDeclinedTaskSearchResult } from '../../../../object-collection/shared/claimed-declined-task-search-result.model'; @@ -21,8 +20,7 @@ import { ClaimedTask } from '../../../../../core/tasks/models/claimed-task-objec @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 }] + templateUrl: './claimed-declined-search-result-list-element.component.html' }) @listableObjectComponent(ClaimedDeclinedTaskSearchResult, ViewMode.ListElement) export class ClaimedDeclinedSearchResultListElementComponent extends SearchResultListElementComponent { @@ -35,7 +33,7 @@ export class ClaimedDeclinedSearchResultListElementComponent extends SearchResul /** * Represent item's status */ - public status = MyDspaceItemStatusType.VALIDATION; + public status = MyDspaceItemStatusType.DECLINED; /** * The workflowitem object that belonging to the result object 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 651ed35dc1..05c54ab03c 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,5 +1,4 @@ import { Component } from '@angular/core'; -import { Location, LocationStrategy, PathLocationStrategy } from '@angular/common'; import { ViewMode } from '../../../../core/shared/view-mode.model'; import { listableObjectComponent } from '../../../object-collection/shared/listable-object/listable-object.decorator'; @@ -17,8 +16,7 @@ import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.m @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 }] + templateUrl: './claimed-search-result-list-element.component.html' }) @listableObjectComponent(ClaimedTaskSearchResult, ViewMode.ListElement) export class ClaimedSearchResultListElementComponent extends SearchResultListElementComponent { From b80282c2431d850da48ca41924b0cd29932a228d Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Tue, 22 Dec 2020 17:32:56 +0100 Subject: [PATCH 05/18] [CSTPER-3620] Workflow Actions refresh entire MyDSpace page instead of just WorkflowItem Test fix and environment settings restored --- ...imed-task-actions-reject.component.spec.ts | 26 +++++++++++++------ src/environments/environment.common.ts | 6 ++--- 2 files changed, 21 insertions(+), 11 deletions(-) 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 d090d12099..8180db9f73 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 @@ -143,17 +143,27 @@ describe('ClaimedTaskActionsRejectComponent', () => { expect(component.startActionExecution).toHaveBeenCalled(); }); - describe('actionExecution', () => { - beforeEach(() => { - component.actionExecution().subscribe(); - fixture.detectChanges(); - }); + }); - it('should call claimedTaskService\'s submitTask', () => { - expect(claimedTaskService.submitTask).toHaveBeenCalledWith(object.id, expectedBody) - }); + describe('actionExecution', () => { + + let expectedBody; + + beforeEach(() => { + spyOn((component.rejectForm as any), 'get').and.returnValue({value: 'required'}); + + expectedBody = { + [component.option]: 'true', + reason: 'required' + }; + + component.actionExecution().subscribe(); + fixture.detectChanges(); }); + it('should call claimedTaskService\'s submitTask with the proper reason', () => { + expect(claimedTaskService.submitTask).toHaveBeenCalledWith(object.id, expectedBody) + }); }); describe('reloadObjectExecution', () => { diff --git a/src/environments/environment.common.ts b/src/environments/environment.common.ts index f107e79c97..eb961a38eb 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: false, - host: 'localhost', - port: 8080, + ssl: true, + host: 'dspace7.4science.cloud', + port: 443, // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript nameSpace: '/server', }, From 5700450eec178dc60a4f3105535adfde1df242db Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Tue, 22 Dec 2020 18:20:22 +0100 Subject: [PATCH 06/18] [CSTPER-3620] Workflow Actions refresh entire MyDSpace page instead of just WorkflowItem Failing tests in CI environment refactored --- ...med-task-actions-approve.component.spec.ts | 22 +++++++++++-------- ...imed-task-actions-reject.component.spec.ts | 11 +++++----- 2 files changed, 18 insertions(+), 15 deletions(-) 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 afcc01c315..c3db0f71d7 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 @@ -102,18 +102,22 @@ describe('ClaimedTaskActionsApproveComponent', () => { it('should start the action execution', () => { expect(component.startActionExecution).toHaveBeenCalled(); }); + }); - describe('actionExecution', () => { - beforeEach(() => { - component.actionExecution().subscribe(); - fixture.detectChanges(); + describe('actionExecution', () => { + + it('should call claimedTaskService\'s submitTask', (done) => { + + const expectedBody = { + [component.option]: 'true' + }; + + component.actionExecution().subscribe(() => { + expect(claimedTaskService.submitTask).toHaveBeenCalledWith(object.id, expectedBody); + done(); }); - - it('should call claimedTaskService\'s submitTask', () => { - expect(claimedTaskService.submitTask).toHaveBeenCalledWith(object.id, expectedBody) - }); - }); + }); describe('reloadObjectExecution', () => { 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 8180db9f73..e0ab714d85 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 @@ -151,18 +151,17 @@ describe('ClaimedTaskActionsRejectComponent', () => { beforeEach(() => { spyOn((component.rejectForm as any), 'get').and.returnValue({value: 'required'}); - expectedBody = { [component.option]: 'true', reason: 'required' }; - - component.actionExecution().subscribe(); - fixture.detectChanges(); }); - it('should call claimedTaskService\'s submitTask with the proper reason', () => { - expect(claimedTaskService.submitTask).toHaveBeenCalledWith(object.id, expectedBody) + it('should call claimedTaskService\'s submitTask with the proper reason', (done) => { + component.actionExecution().subscribe(() => { + expect(claimedTaskService.submitTask).toHaveBeenCalledWith(object.id, expectedBody); + done(); + }); }); }); From d542072a37148259e338fed28c33d75cf823467f Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Thu, 7 Jan 2021 15:26:55 +0100 Subject: [PATCH 07/18] [CSTPER-3620] Workflow Actions refresh entire MyDSpace page instead of just WorkflowItem Fixed test. Removed unused imports. --- src/app/core/tasks/tasks.service.ts | 9 ++++----- .../abstract/claimed-task-actions-abstract.component.ts | 2 +- .../approve/claimed-task-actions-approve.component.ts | 5 ----- .../reject/claimed-task-actions-reject.component.ts | 5 ----- .../listable-object-component-loader.component.spec.ts | 1 - 5 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/app/core/tasks/tasks.service.ts b/src/app/core/tasks/tasks.service.ts index 8638bb30e6..ed58eb920e 100644 --- a/src/app/core/tasks/tasks.service.ts +++ b/src/app/core/tasks/tasks.service.ts @@ -1,7 +1,7 @@ import { HttpHeaders } from '@angular/common/http'; -import { merge as observableMerge, Observable, of as observableOf } from 'rxjs'; -import { distinctUntilChanged, filter, find, flatMap, map, mergeMap, switchMap, tap } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import { distinctUntilChanged, filter, find, flatMap, map, switchMap, tap } from 'rxjs/operators'; import { DataService } from '../data/data.service'; import { @@ -13,12 +13,11 @@ import { } from '../data/request.models'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { ProcessTaskResponse } from './models/process-task-response'; -import {getFirstCompletedRemoteData, getResponseFromEntry} from '../shared/operators'; -import { ErrorResponse, MessageResponse, RestResponse } from '../cache/response.models'; +import { getFirstCompletedRemoteData } from '../shared/operators'; 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'; +import { HttpOptions } from '../dspace-rest/dspace-rest.service'; /** * An abstract class that provides methods to handle task requests. 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 eb43a33a20..b9a424c57f 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 @@ -10,7 +10,7 @@ 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 { 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'; 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 1f4111a14a..d73da460b7 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 @@ -49,9 +49,4 @@ export class ClaimedTaskActionsApproveComponent extends ClaimedTaskActionsAbstra return reloadedObject; } - // mock - actionExecution(): Observable { - return of({ hasSucceeded: 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 267f978392..911bd385f4 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 @@ -99,9 +99,4 @@ export class ClaimedTaskActionsRejectComponent extends ClaimedTaskActionsAbstrac }); return reloadedObject; } - - // mock - actionExecution(): Observable { - return of({ hasSucceeded: true}); - } } 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 44bc5c28f7..b7221b4632 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 @@ -12,7 +12,6 @@ import { spyOnExported } from '../../../testing/utils.test'; import { TranslateModule } from '@ngx-translate/core'; import { By } from '@angular/platform-browser'; import { Item } from '../../../../core/shared/item.model'; -import { By } from '@angular/platform-browser'; const testType = 'TestType'; const testContext = Context.Search; From 2bd80840466268ee65916568d98f0638f7051b51 Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Mon, 11 Jan 2021 16:49:18 +0100 Subject: [PATCH 08/18] [CSTPER-3620] Workflow Actions refresh entire MyDSpace page instead of just WorkflowItem Provided mock and stub instead of empty objects --- src/app/core/tasks/tasks.service.spec.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/core/tasks/tasks.service.spec.ts b/src/app/core/tasks/tasks.service.spec.ts index 17e8cb5983..1bb4ea46a5 100644 --- a/src/app/core/tasks/tasks.service.spec.ts +++ b/src/app/core/tasks/tasks.service.spec.ts @@ -19,6 +19,8 @@ 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'; +import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; const LINK_NAME = 'test'; @@ -57,8 +59,8 @@ describe('TasksService', () => { const linkPath = 'testTask'; const requestService = getMockRequestService(); const halService: any = new HALEndpointServiceStub(taskEndpoint); - const rdbService = {} as RemoteDataBuildService; - const notificationsService = {} as NotificationsService; + const rdbService = getMockRemoteDataBuildService(); + const notificationsService = new NotificationsServiceStub() as any; const http = {} as HttpClient; const comparator = new DummyChangeAnalyzer() as any; const objectCache = { From 258e48e9a2f3f9e1dc1284276b679bd921c9198f Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Mon, 11 Jan 2021 17:39:35 +0100 Subject: [PATCH 09/18] [CSTPER-3620] Workflow Actions refresh entire MyDSpace page instead of just WorkflowItem Text fix --- src/app/core/tasks/tasks.service.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/core/tasks/tasks.service.spec.ts b/src/app/core/tasks/tasks.service.spec.ts index 1bb4ea46a5..a2ba10f320 100644 --- a/src/app/core/tasks/tasks.service.spec.ts +++ b/src/app/core/tasks/tasks.service.spec.ts @@ -21,6 +21,7 @@ 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'; import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; +import { of } from 'rxjs'; const LINK_NAME = 'test'; @@ -124,7 +125,7 @@ describe('TasksService', () => { it('should call findByHref with the href generated by getSearchByHref', () => { spyOn(service, 'getSearchByHref').and.returnValue(observableOf('generatedHref')); - spyOn(service, 'findByHref').and.returnValue(null); + spyOn(service, 'findByHref').and.returnValue(of(null)); const followLinks = {}; const options = new FindListOptions(); From 2becaca1b0e48ae5e6295cdcc5d431c255346d4c Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Tue, 19 Jan 2021 09:10:24 +0100 Subject: [PATCH 10/18] [CSTPER-3620] Workflow Actions refresh entire MyDSpace page instead of just WorkflowItem Fixes and debug log --- .../core/tasks/claimed-task-data.service.ts | 11 +++++++-- src/app/core/tasks/pool-task-data.service.ts | 6 +++-- src/app/core/tasks/tasks.service.ts | 8 +++++-- ...claimed-task-actions-abstract.component.ts | 18 ++++++++++----- .../claimed-task-actions.component.ts | 13 +++++++---- ...d-task-actions-return-to-pool.component.ts | 6 ++++- .../mydspace-actions/mydspace-actions.ts | 6 +++++ .../mydspace-reloadable-actions.ts | 2 ++ .../pool-task/pool-task-actions.component.ts | 23 ++++++++++++++----- ...table-object-component-loader.component.ts | 2 ++ 10 files changed, 72 insertions(+), 23 deletions(-) diff --git a/src/app/core/tasks/claimed-task-data.service.ts b/src/app/core/tasks/claimed-task-data.service.ts index 99c2f51117..b9c7e9ee54 100644 --- a/src/app/core/tasks/claimed-task-data.service.ts +++ b/src/app/core/tasks/claimed-task-data.service.ts @@ -20,6 +20,7 @@ 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'; +import { getFirstSucceededRemoteData } from '../shared/operators'; /** * The service handling all REST requests for ClaimedTask @@ -28,7 +29,7 @@ import { HttpOptions } from '../dspace-rest/dspace-rest.service'; @dataService(CLAIMED_TASK) export class ClaimedTaskDataService extends TasksService { - protected responseMsToLive = 10 * 1000; + protected responseMsToLive = 1000; /** * The endpoint link name @@ -68,6 +69,8 @@ export class ClaimedTaskDataService extends TasksService { * Emit the server response */ public claimTask(scopeId: string, poolTaskHref: string): Observable { + console.log('=========================================='); + console.log('User ClaimTask request for:', scopeId, poolTaskHref); const options: HttpOptions = Object.create({}); let headers = new HttpHeaders(); headers = headers.append('Content-Type', 'text/uri-list'); @@ -98,6 +101,8 @@ export class ClaimedTaskDataService extends TasksService { * Emit the server response */ public returnToPoolTask(scopeId: string): Observable { + console.log('=========================================='); + console.log('User ReturnToPool request for:', scopeId); return this.deleteById(this.linkPath, scopeId, this.makeHttpOptions()); } @@ -109,11 +114,13 @@ export class ClaimedTaskDataService extends TasksService { * The server response */ public findByItem(uuid: string): Observable> { + console.log('claimedTaskService findByItem', uuid); const options = new FindListOptions(); options.searchParams = [ new RequestParam('uuid', uuid) ]; - return this.searchTask('findByItem', options, followLink('workflowitem')); + return this.searchTask('findByItem', options, followLink('workflowitem')) + .pipe(getFirstSucceededRemoteData()); } } diff --git a/src/app/core/tasks/pool-task-data.service.ts b/src/app/core/tasks/pool-task-data.service.ts index 78348d428f..26eb3e6dad 100644 --- a/src/app/core/tasks/pool-task-data.service.ts +++ b/src/app/core/tasks/pool-task-data.service.ts @@ -18,6 +18,7 @@ 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 { getFirstSucceededRemoteData } from '../shared/operators'; /** * The service handling all REST requests for PoolTask @@ -31,7 +32,7 @@ export class PoolTaskDataService extends TasksService { */ protected linkPath = 'pooltasks'; - protected responseMsToLive = 10 * 1000; + protected responseMsToLive = 1000; /** * Initialize instance variables @@ -70,7 +71,8 @@ export class PoolTaskDataService extends TasksService { options.searchParams = [ new RequestParam('uuid', uuid) ]; - return this.searchTask('findByItem', options, followLink('workflowitem')); + return this.searchTask('findByItem', options, followLink('workflowitem')) + .pipe(getFirstSucceededRemoteData()); } /** diff --git a/src/app/core/tasks/tasks.service.ts b/src/app/core/tasks/tasks.service.ts index ed58eb920e..8032238c45 100644 --- a/src/app/core/tasks/tasks.service.ts +++ b/src/app/core/tasks/tasks.service.ts @@ -13,7 +13,7 @@ import { } from '../data/request.models'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { ProcessTaskResponse } from './models/process-task-response'; -import { getFirstCompletedRemoteData } from '../shared/operators'; +import {getFirstCompletedRemoteData, getFirstSucceededRemoteData} from '../shared/operators'; import { CacheableObject } from '../cache/object-cache.reducer'; import { RemoteData } from '../data/remote-data'; import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; @@ -140,7 +140,11 @@ export abstract class TasksService extends DataServic const hrefObs = this.getSearchByHref(searchMethod, options, ...linksToFollow); return hrefObs.pipe( find((href: string) => hasValue(href)), - switchMap((href) => this.findByHref(href)) + switchMap((href) => this.findByHref(href).pipe( + getFirstSucceededRemoteData(), + tap(() => { + this.requestService.setStaleByHrefSubstring(searchMethod) + }))), ); } } 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 b9a424c57f..6b3bf0a20b 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,4 +1,4 @@ -import { Injector } from '@angular/core'; +import {Injector, OnDestroy} from '@angular/core'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service'; import { DSpaceObject} from '../../../../core/shared/dspace-object.model'; @@ -10,7 +10,7 @@ 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 { switchMap } from 'rxjs/operators'; +import {switchMap, take} 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'; @@ -23,7 +23,7 @@ import { MyDSpaceReloadableActionsComponent } from '../../mydspace-reloadable-ac * - 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 extends MyDSpaceReloadableActionsComponent { +export abstract class ClaimedTaskActionsAbstractComponent extends MyDSpaceReloadableActionsComponent implements OnDestroy { /** * The workflow task option the child component represents @@ -37,6 +37,8 @@ export abstract class ClaimedTaskActionsAbstractComponent extends MyDSpaceReload */ itemUuid: string; + subs = []; + protected constructor(protected injector: Injector, protected router: Router, protected notificationsService: NotificationsService, @@ -50,7 +52,7 @@ export abstract class ClaimedTaskActionsAbstractComponent extends MyDSpaceReload * Submit the action on the claimed object. */ submitTask() { - this.startActionExecution().subscribe(); + this.subs.push(this.startActionExecution().pipe(take(1)).subscribe()); } /** @@ -82,13 +84,17 @@ export abstract class ClaimedTaskActionsAbstractComponent extends MyDSpaceReload if (!(this.object as any).workflowitem) { return; } - this.object.workflowitem.pipe( + this.subs.push(this.object.workflowitem.pipe( getFirstSucceededRemoteDataPayload(), switchMap((workflowItem: WorkflowItem) => workflowItem.item.pipe(getFirstSucceededRemoteDataPayload()) )) .subscribe((item: Item) => { this.itemUuid = item.uuid; - }) + })); + } + + ngOnDestroy() { + this.subs.forEach((sub) => sub.unsubscribe()); } } diff --git a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts index c82154af09..ae801c7586 100644 --- a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts @@ -1,8 +1,8 @@ -import { Component, Injector, Input, OnInit } from '@angular/core'; +import {Component, Injector, Input, OnDestroy, OnInit} from '@angular/core'; import { Router } from '@angular/router'; import { Observable } from 'rxjs'; -import { filter, map } from 'rxjs/operators'; +import { filter, map, take } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service'; @@ -26,7 +26,7 @@ import { WORKFLOW_TASK_OPTION_RETURN_TO_POOL } from './return-to-pool/claimed-ta styleUrls: ['./claimed-task-actions.component.scss'], templateUrl: './claimed-task-actions.component.html', }) -export class ClaimedTaskActionsComponent extends MyDSpaceActionsComponent implements OnInit { +export class ClaimedTaskActionsComponent extends MyDSpaceActionsComponent implements OnInit, OnDestroy { /** * The ClaimedTask object @@ -87,7 +87,8 @@ export class ClaimedTaskActionsComponent extends MyDSpaceActionsComponent>).pipe( filter((rd: RemoteData) => ((!rd.isRequestPending) && isNotUndefined(rd.payload))), - map((rd: RemoteData) => rd.payload)); + map((rd: RemoteData) => rd.payload), + take(1)); } /** @@ -99,4 +100,8 @@ export class ClaimedTaskActionsComponent extends MyDSpaceActionsComponent | DSpaceObject> { - return this.poolTaskService.findByItem(this.itemUuid); + return this.poolTaskService.findByItem(this.itemUuid).pipe(take(1), tap((value) => { + console.log('The new PoolTask (found by item) is:', value); + })); } actionExecution(): Observable { diff --git a/src/app/shared/mydspace-actions/mydspace-actions.ts b/src/app/shared/mydspace-actions/mydspace-actions.ts index 373cab9d35..2307f8a430 100644 --- a/src/app/shared/mydspace-actions/mydspace-actions.ts +++ b/src/app/shared/mydspace-actions/mydspace-actions.ts @@ -82,6 +82,8 @@ export abstract class MyDSpaceActionsComponent) => rd.hasSucceeded) @@ -115,6 +119,8 @@ export abstract class MyDSpaceActionsComponent { +export class PoolTaskActionsComponent extends MyDSpaceReloadableActionsComponent implements OnDestroy { /** * The PoolTask object @@ -45,6 +46,8 @@ export class PoolTaskActionsComponent extends MyDSpaceReloadableActionsComponent */ public itemUuid: string; + subs = []; + /** * Initialize instance variables * @@ -69,7 +72,7 @@ export class PoolTaskActionsComponent extends MyDSpaceReloadableActionsComponent * Claim the task. */ claim() { - this.startActionExecution().subscribe(); + this.subs.push(this.startActionExecution().pipe(take(1)).subscribe()); } /** @@ -81,7 +84,8 @@ export class PoolTaskActionsComponent extends MyDSpaceReloadableActionsComponent this.object = object; this.workflowitem$ = (this.object.workflowitem as Observable>).pipe( filter((rd: RemoteData) => ((!rd.isRequestPending) && isNotUndefined(rd.payload))), - map((rd: RemoteData) => rd.payload)); + map((rd: RemoteData) => rd.payload), + take(1)); } actionExecution(): Observable { @@ -92,7 +96,9 @@ export class PoolTaskActionsComponent extends MyDSpaceReloadableActionsComponent } reloadObjectExecution(): Observable | DSpaceObject> { - return this.claimedTaskService.findByItem(this.itemUuid); + return this.claimedTaskService.findByItem(this.itemUuid).pipe(take(1), tap((value) => { + console.log('The new ClaimTask (found by item) is:', value); + })); } /** @@ -108,4 +114,9 @@ export class PoolTaskActionsComponent extends MyDSpaceReloadableActionsComponent }) } + ngOnDestroy() { + this.subs.forEach((sub) => sub.unsubscribe()); + console.log('Destroy of PoolTaskActionsComponent', this.object) + } + } 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 175bdebbab..ee64c63ca5 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 @@ -135,6 +135,8 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnDestroy if ((componentRef.instance as any).reloadedObject) { this.subs.push((componentRef.instance as any).reloadedObject.subscribe((reloadedObject: DSpaceObject) => { if (reloadedObject) { + console.log('Reloaded Object from/to', this.object, reloadedObject); + this.object = reloadedObject; this.instantiateComponent(reloadedObject); } })); From c65dfc7303211580de9011fb7bfb4f6271a0f54d Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Fri, 29 Jan 2021 18:45:41 +0100 Subject: [PATCH 11/18] [CSTPER-3620] Workflow Actions refresh entire MyDSpace page instead of just WorkflowItem Fixed test after angular 10 migration --- src/app/+my-dspace-page/my-dspace-search.module.ts | 4 ++++ src/app/core/tasks/tasks.service.ts | 11 +++++------ .../claimed-task-actions-abstract.component.ts | 14 +++++++------- .../claimed-task-actions-reject.component.spec.ts | 4 +--- ...d-task-actions-return-to-pool.component.spec.ts | 6 +++--- .../claimed-task-actions-loader.component.spec.ts | 14 +++++--------- .../claimed-task-actions-loader.component.ts | 7 ++++++- .../shared/mydspace-actions/mydspace-actions.ts | 10 +++++----- .../mydspace-reloadable-actions.ts | 13 ++++++++----- ...table-object-component-loader.component.spec.ts | 11 ++++------- .../listable-object-component-loader.component.ts | 8 +++++--- ...-search-result-detail-element.component.spec.ts | 4 ++-- 12 files changed, 55 insertions(+), 51 deletions(-) diff --git a/src/app/+my-dspace-page/my-dspace-search.module.ts b/src/app/+my-dspace-page/my-dspace-search.module.ts index 2fe1cd2a55..a97f2207e7 100644 --- a/src/app/+my-dspace-page/my-dspace-search.module.ts +++ b/src/app/+my-dspace-page/my-dspace-search.module.ts @@ -14,12 +14,16 @@ import { ClaimedTaskSearchResultDetailElementComponent } from '../shared/object- import { ItemSearchResultListElementSubmissionComponent } from '../shared/object-list/my-dspace-result-list-element/item-search-result/item-search-result-list-element-submission.component'; 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 { 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'; const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator WorkspaceItemSearchResultListElementComponent, WorkflowItemSearchResultListElementComponent, ClaimedSearchResultListElementComponent, + ClaimedApprovedSearchResultListElementComponent, + ClaimedDeclinedSearchResultListElementComponent, PoolSearchResultListElementComponent, ItemSearchResultDetailElementComponent, WorkspaceItemSearchResultDetailElementComponent, diff --git a/src/app/core/tasks/tasks.service.ts b/src/app/core/tasks/tasks.service.ts index 014e2ba9d3..842a98301f 100644 --- a/src/app/core/tasks/tasks.service.ts +++ b/src/app/core/tasks/tasks.service.ts @@ -1,18 +1,17 @@ import { HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs'; -import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators'; +import { distinctUntilChanged, filter, find, map, mergeMap, switchMap, tap } from 'rxjs/operators'; import { DataService } from '../data/data.service'; -import { DeleteRequest, PostRequest, TaskDeleteRequest, TaskPostRequest } from '../data/request.models'; -import { isNotEmpty } from '../../shared/empty.util'; +import { DeleteRequest, FindListOptions, PostRequest, TaskDeleteRequest, TaskPostRequest } from '../data/request.models'; +import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { HttpOptions } from '../dspace-rest/dspace-rest.service'; import { ProcessTaskResponse } from './models/process-task-response'; import { getFirstCompletedRemoteData, getFirstSucceededRemoteData } from '../shared/operators'; 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. @@ -131,14 +130,14 @@ export abstract class TasksService extends DataServic * @param linksToFollow * links to follow */ - public searchTask(searchMethod: string, options: FindListOptions = {}, ...linksToFollow: Array>): Observable> { + public searchTask(searchMethod: string, options: FindListOptions = {}, ...linksToFollow: FollowLinkConfig[]): Observable> { const hrefObs = this.getSearchByHref(searchMethod, options, ...linksToFollow); return hrefObs.pipe( find((href: string) => hasValue(href)), switchMap((href) => this.findByHref(href).pipe( getFirstSucceededRemoteData(), tap(() => { - this.requestService.setStaleByHrefSubstring(searchMethod) + this.requestService.setStaleByHrefSubstring(searchMethod); }))), ); } 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 fdf13e4f2c..fb23b4feb1 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,4 +1,4 @@ -import {Injector, OnDestroy} from '@angular/core'; +import { Component, Injector, OnDestroy } from '@angular/core'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service'; import { DSpaceObject} from '../../../../core/shared/dspace-object.model'; @@ -10,7 +10,7 @@ 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 {switchMap, take} from 'rxjs/operators'; +import { switchMap, take } 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'; @@ -23,10 +23,10 @@ import { MyDSpaceReloadableActionsComponent } from '../../mydspace-reloadable-ac * - 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 */ -// @Component({ -// selector: 'ds-calim-task-action-abstract', -// template: '' -// }) +@Component({ + selector: 'ds-claimed-task-action-abstract', + template: '' +}) export abstract class ClaimedTaskActionsAbstractComponent extends MyDSpaceReloadableActionsComponent implements OnDestroy { /** @@ -74,7 +74,7 @@ export abstract class ClaimedTaskActionsAbstractComponent extends MyDSpaceReload } actionExecution(): Observable { - return this.objectDataService.submitTask(this.object.id, this.createbody()) + return this.objectDataService.submitTask(this.object.id, this.createbody()); } initObjects(object: ClaimedTask) { 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 206d7b4720..5d3a2ec127 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,6 +1,4 @@ import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core'; -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; @@ -39,7 +37,7 @@ let mockPoolTaskDataService: PoolTaskDataService; describe('ClaimedTaskActionsRejectComponent', () => { const object = Object.assign(new ClaimedTask(), { id: 'claimed-task-1' }); const claimedTaskService = jasmine.createSpyObj('claimedTaskService', { - submitTask: observableOf(new ProcessTaskResponse(true)) + submitTask: of(new ProcessTaskResponse(true)) }); beforeEach(waitForAsync(() => { 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 b2d949b18c..e53daccf0d 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, Injector, NO_ERRORS_SCHEMA } from '@angular/core'; -import { async, ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed, waitForAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; @@ -31,7 +31,7 @@ let mockPoolTaskDataService: PoolTaskDataService; describe('ClaimedTaskActionsReturnToPoolComponent', () => { const object = Object.assign(new ClaimedTask(), { id: 'claimed-task-1' }); const claimedTaskService = jasmine.createSpyObj('claimedTaskService', { - returnToPoolTask: observableOf(new ProcessTaskResponse(true)) + returnToPoolTask: of(new ProcessTaskResponse(true)) }); beforeEach(waitForAsync(() => { @@ -91,7 +91,7 @@ describe('ClaimedTaskActionsReturnToPoolComponent', () => { }); it('should call claimedTaskService\'s returnToPoolTask', () => { - expect(claimedTaskService.returnToPoolTask).toHaveBeenCalledWith(object.id) + expect(claimedTaskService.returnToPoolTask).toHaveBeenCalledWith(object.id); }); }); diff --git a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts index d972721338..6de2056fe8 100644 --- a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts +++ b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts @@ -1,13 +1,11 @@ import { ClaimedTaskActionsLoaderComponent } from './claimed-task-actions-loader.component'; import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; -import { ChangeDetectionStrategy, ComponentFactoryResolver, Injector, NO_ERRORS_SCHEMA } from '@angular/core'; -import * as decorators from './claimed-task-actions-decorator'; +import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core'; import { ClaimedTaskActionsDirective } from './claimed-task-actions.directive'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; import { TranslateModule } from '@ngx-translate/core'; import { ClaimedTaskActionsEditMetadataComponent } from '../edit-metadata/claimed-task-actions-edit-metadata.component'; import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service'; -import { spyOnExported } from '../../../testing/utils.test'; import { NotificationsService } from '../../../notifications/notifications.service'; import { NotificationsServiceStub } from '../../../testing/notifications-service.stub'; import { Router } from '@angular/router'; @@ -22,7 +20,6 @@ const searchService = getMockSearchService(); const requestService = getMockRequestService(); -//xdescribe describe('ClaimedTaskActionsLoaderComponent', () => { let comp: ClaimedTaskActionsLoaderComponent; let fixture: ComponentFixture; @@ -42,8 +39,7 @@ describe('ClaimedTaskActionsLoaderComponent', () => { { provide: Router, useValue: new RouterStub() }, { provide: SearchService, useValue: searchService }, { provide: RequestService, useValue: requestService }, - { provide: PoolTaskDataService, useValue: {} }, - ComponentFactoryResolver + { provide: PoolTaskDataService, useValue: {} } ] }).overrideComponent(ClaimedTaskActionsLoaderComponent, { set: { @@ -56,16 +52,16 @@ describe('ClaimedTaskActionsLoaderComponent', () => { beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(ClaimedTaskActionsLoaderComponent); comp = fixture.componentInstance; - comp.object = object; comp.option = option; - spyOnExported(decorators, 'getComponentByWorkflowTaskOption').and.returnValue(ClaimedTaskActionsEditMetadataComponent); + spyOn(comp, 'getComponentByWorkflowTaskOption').and.returnValue(ClaimedTaskActionsEditMetadataComponent); + fixture.detectChanges(); })); describe('When the component is rendered', () => { it('should call the getComponentByWorkflowTaskOption function with the right option', () => { - expect(decorators.getComponentByWorkflowTaskOption).toHaveBeenCalledWith(option); + expect(comp.getComponentByWorkflowTaskOption).toHaveBeenCalledWith(option); }); }); }); 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 d9b2bf2985..68c597a41c 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 @@ -59,7 +59,8 @@ export class ClaimedTaskActionsLoaderComponent implements OnInit, OnDestroy { * Fetch, create and initialize the relevant component */ ngOnInit(): void { - const comp = getComponentByWorkflowTaskOption(this.option); + + const comp = this.getComponentByWorkflowTaskOption(this.option); if (hasValue(comp)) { const componentFactory = this.componentFactoryResolver.resolveComponentFactory(comp); @@ -75,6 +76,10 @@ export class ClaimedTaskActionsLoaderComponent implements OnInit, OnDestroy { } } + getComponentByWorkflowTaskOption(option: string) { + return getComponentByWorkflowTaskOption(option); + } + /** * Unsubscribe from open subscriptions */ diff --git a/src/app/shared/mydspace-actions/mydspace-actions.ts b/src/app/shared/mydspace-actions/mydspace-actions.ts index f567e760a0..bc47b61a01 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 { EventEmitter, Injector, Input, Output } from '@angular/core'; +import { Component, EventEmitter, Injector, Input, Output } from '@angular/core'; import { find, take, tap } from 'rxjs/operators'; @@ -23,10 +23,10 @@ export interface MyDSpaceActionsResult { /** * Abstract class for all different representations of mydspace actions */ -// @Component({ -// selector: 'ds-mydspace-actions-abstract', -// template: '' -// }) +@Component({ + selector: 'ds-mydspace-actions-abstract', + template: '' +}) export abstract class MyDSpaceActionsComponent> { /** diff --git a/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts b/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts index 05585101e6..2a9a14a341 100644 --- a/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts +++ b/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts @@ -1,5 +1,5 @@ import { Router } from '@angular/router'; -import { Injector, OnInit } from '@angular/core'; +import { Component, Injector, OnInit } from '@angular/core'; import { map, switchMap, tap} from 'rxjs/operators'; @@ -18,11 +18,14 @@ import { ProcessTaskResponse } from '../../core/tasks/models/process-task-respon import { getFirstCompletedRemoteData } from '../../core/shared/operators'; import { getSearchResultFor } from '../search/search-result-element-decorator'; import { MyDSpaceActionsComponent } from './mydspace-actions'; -import {Subscription} from 'rxjs/internal/Subscription'; /** * Abstract class for all different representations of mydspace actions */ +@Component({ + selector: 'ds-mydspace-reloadable-actions', + template: '' +}) export abstract class MyDSpaceReloadableActionsComponent> extends MyDSpaceActionsComponent implements OnInit { @@ -71,7 +74,7 @@ export abstract class MyDSpaceReloadableActionsComponent { if (res instanceof RemoteData) { - return of(res).pipe(getFirstCompletedRemoteData(), map((completed) => completed.payload)) + return of(res).pipe(getFirstCompletedRemoteData(), map((completed) => completed.payload)); } else { return of(res); } })).pipe(map((dso) => { return dso ? this.convertReloadedObject(dso) : dso; - })) + })); } } 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 46d6ba2c56..74dc0be623 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 @@ -5,10 +5,8 @@ import { ListableObject } from '../listable-object.model'; import { GenericConstructor } from '../../../../core/shared/generic-constructor'; import { Context } from '../../../../core/shared/context.model'; import { ViewMode } from '../../../../core/shared/view-mode.model'; -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 { TranslateModule } from '@ngx-translate/core'; import { By } from '@angular/platform-browser'; import { Item } from '../../../../core/shared/item.model'; @@ -23,7 +21,6 @@ class TestType extends ListableObject { } } -//xdescribe describe('ListableObjectComponentLoaderComponent', () => { let comp: ListableObjectComponentLoaderComponent; let fixture: ComponentFixture; @@ -33,7 +30,7 @@ describe('ListableObjectComponentLoaderComponent', () => { imports: [TranslateModule.forRoot()], declarations: [ListableObjectComponentLoaderComponent, ItemListElementComponent, ListableObjectDirective], schemas: [NO_ERRORS_SCHEMA], - providers: [ComponentFactoryResolver] + providers: [] }).overrideComponent(ListableObjectComponentLoaderComponent, { set: { changeDetection: ChangeDetectionStrategy.Default, @@ -49,14 +46,14 @@ describe('ListableObjectComponentLoaderComponent', () => { comp.object = new TestType(); comp.viewMode = testViewMode; comp.context = testContext; - spyOnExported(listableObjectDecorators, 'getListableObjectComponent').and.returnValue(ItemListElementComponent); + spyOn(comp, 'getComponent').and.returnValue(ItemListElementComponent as any); fixture.detectChanges(); })); describe('When the component is rendered', () => { it('should call the getListableObjectComponent function with the right types, view mode and context', () => { - expect(listableObjectDecorators.getListableObjectComponent).toHaveBeenCalledWith([testType], testViewMode, testContext); + expect(comp.getComponent).toHaveBeenCalledWith([testType], testViewMode, testContext); }); }); @@ -130,7 +127,7 @@ describe('ListableObjectComponentLoaderComponent', () => { 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 a701e10340..28a906f2fe 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 @@ -111,7 +111,9 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnDestroy this.initBadges(); - const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent(object)); + const component = this.getComponent(object.getRenderTypes(), this.viewMode, this.context); + + const componentFactory = this.componentFactoryResolver.resolveComponentFactory(component); const viewContainerRef = this.listableObjectDirective.viewContainerRef; viewContainerRef.clear(); @@ -160,7 +162,7 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnDestroy * Fetch the component depending on the item's relationship type, view mode and context * @returns {GenericConstructor} */ - private getComponent(object): GenericConstructor { - return getListableObjectComponent(object.getRenderTypes(), this.viewMode, this.context); + getComponent(object, viewMode: ViewMode, context: Context): GenericConstructor { + return getListableObjectComponent(object.getRenderTypes(), viewMode, context); } } 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 b570e59ae6..a6a3e2020b 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 { ComponentFixture, TestBed, waitForAsyncfakeAsync, tick } from '@angular/core/testing'; +import { ComponentFixture, TestBed, tick, waitForAsync, fakeAsync} from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { of as observableOf } from 'rxjs'; @@ -105,7 +105,7 @@ describe('ClaimedTaskSearchResultDetailElementComponent', () => { const actionPayload: any = { reloadedObject: {}}; const actionsComponent = fixture.debugElement.query(By.css('ds-claimed-task-actions')); - actionsComponent.triggerEventHandler('processCompleted', actionPayload) + actionsComponent.triggerEventHandler('processCompleted', actionPayload); tick(); expect(component.reloadedObject.emit).toHaveBeenCalledWith(actionPayload.reloadedObject); From 54c405994cfa1737611c15e26db489058f017af6 Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Fri, 29 Jan 2021 19:31:54 +0100 Subject: [PATCH 12/18] [CSTPER-3620] Workflow Actions refresh entire MyDSpace page instead of just WorkflowItem Lint --- .../claimed-task-actions.component.ts | 2 +- .../mydspace-actions/mydspace-actions.ts | 6 ++--- .../mydspace-reloadable-actions.spec.ts | 24 +++++++++---------- .../pool-task-actions.component.spec.ts | 6 ++--- .../pool-task/pool-task-actions.component.ts | 8 +++---- ...table-object-component-loader.component.ts | 6 +++-- ...ch-result-detail-element.component.spec.ts | 2 +- ...arch-result-list-element.component.spec.ts | 4 ++-- ...-list-element-submission.component.spec.ts | 2 +- ...arch-result-list-element.component.spec.ts | 4 ++-- ...arch-result-list-element.component.spec.ts | 2 +- ...arch-result-list-element.component.spec.ts | 2 +- 12 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts index ae801c7586..afb50c8351 100644 --- a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts @@ -101,7 +101,7 @@ export class ClaimedTaskActionsComponent extends MyDSpaceActionsComponent { - beforeEach(async(() => { - mockDataService = new PoolTaskDataService(null, null, null, null, null, null, null, null) + beforeEach(fakeAsync(() => { + 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: [ @@ -118,7 +118,7 @@ describe('MyDSpaceReloadableActionsComponent', () => { spyOn(component, 'initObjects'); }); - it('should call initReloadAnchor and initObjects on init', async(() => { + it('should call initReloadAnchor and initObjects on init', fakeAsync(() => { component.ngOnInit(); fixture.detectChanges(); @@ -130,7 +130,7 @@ describe('MyDSpaceReloadableActionsComponent', () => { })); - }) + }); describe('on action execution fail', () => { @@ -158,7 +158,7 @@ describe('MyDSpaceReloadableActionsComponent', () => { component.startActionExecution().subscribe( (result) => { expect(notificationsServiceStub.error).toHaveBeenCalled(); done(); - }) + }); }); it('should not call reloadObject', (done) => { @@ -166,7 +166,7 @@ describe('MyDSpaceReloadableActionsComponent', () => { component.startActionExecution().subscribe( (result) => { expect(component.reloadObjectExecution).not.toHaveBeenCalled(); done(); - }) + }); }); @@ -175,7 +175,7 @@ describe('MyDSpaceReloadableActionsComponent', () => { component.startActionExecution().subscribe( (result) => { expect(component.processCompleted.emit).not.toHaveBeenCalled(); done(); - }) + }); }); @@ -206,7 +206,7 @@ describe('MyDSpaceReloadableActionsComponent', () => { component.startActionExecution().subscribe( (result) => { expect(component.reloadObjectExecution).toHaveBeenCalled(); done(); - }) + }); }); it('should convert the reloaded object', (done) => { @@ -214,7 +214,7 @@ describe('MyDSpaceReloadableActionsComponent', () => { component.startActionExecution().subscribe( (result) => { expect(component.convertReloadedObject).toHaveBeenCalled(); done(); - }) + }); }); it('should emit the reloaded object in case of success', (done) => { @@ -222,7 +222,7 @@ describe('MyDSpaceReloadableActionsComponent', () => { component.startActionExecution().subscribe( (result) => { expect(component.processCompleted.emit).toHaveBeenCalledWith({result: true, reloadedObject: result as any}); done(); - }) + }); }); }); @@ -252,7 +252,7 @@ describe('MyDSpaceReloadableActionsComponent', () => { component.startActionExecution().subscribe( (result) => { expect(component.reload).toHaveBeenCalled(); done(); - }) + }); }); }); 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 74e3c6748b..bce1f1a467 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 { waitForAsync, ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing'; +import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; import { By } from '@angular/platform-browser'; @@ -75,7 +75,7 @@ mockObject = Object.assign(new PoolTask(), { workflowitem: observableOf(rdWorkfl describe('PoolTaskActionsComponent', () => { beforeEach(waitForAsync(() => { - mockDataService = new PoolTaskDataService(null, null, null, null, null, null, null, null) + 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: [ @@ -160,7 +160,7 @@ describe('PoolTaskActionsComponent', () => { expect(notificationsServiceStub.success).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 f36883b05f..19f257f512 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,4 +1,4 @@ -import {Component, Injector, Input, OnDestroy} from '@angular/core'; +import { Component, Injector, Input, OnDestroy } from '@angular/core'; import { Router } from '@angular/router'; import { Observable } from 'rxjs'; @@ -92,7 +92,7 @@ export class PoolTaskActionsComponent extends MyDSpaceReloadableActionsComponent return this.objectDataService.getPoolTaskEndpointById(this.object.id) .pipe(switchMap((poolTaskHref) => { return this.claimedTaskService.claimTask(this.object.id, poolTaskHref); - })) + })); } reloadObjectExecution(): Observable | DSpaceObject> { @@ -111,12 +111,12 @@ export class PoolTaskActionsComponent extends MyDSpaceReloadableActionsComponent )) .subscribe((item: Item) => { this.itemUuid = item.uuid; - }) + }); } ngOnDestroy() { this.subs.forEach((sub) => sub.unsubscribe()); - console.log('Destroy of PoolTaskActionsComponent', this.object) + console.log('Destroy of PoolTaskActionsComponent', this.object); } } 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 28a906f2fe..9fc27d65bf 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 @@ -162,7 +162,9 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnDestroy * Fetch the component depending on the item's relationship type, view mode and context * @returns {GenericConstructor} */ - getComponent(object, viewMode: ViewMode, context: Context): GenericConstructor { - return getListableObjectComponent(object.getRenderTypes(), viewMode, context); + getComponent(renderTypes: (string | GenericConstructor)[], + viewMode: ViewMode, + context: Context): GenericConstructor { + return getListableObjectComponent(renderTypes, viewMode, context); } } 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 1f5d7e0f7c..f5f19fc041 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 @@ -105,7 +105,7 @@ describe('PoolSearchResultDetailElementComponent', () => { 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) + 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/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 07ffe28360..5dad421f68 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, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { of as observableOf } from 'rxjs'; @@ -104,7 +104,7 @@ describe('ClaimedSearchResultListElementComponent', () => { const actionPayload: any = { reloadedObject: {}}; const actionsComponent = fixture.debugElement.query(By.css('ds-claimed-task-actions')); - actionsComponent.triggerEventHandler('processCompleted', actionPayload) + 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/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 0d02a80976..e2017e8748 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 @@ -80,7 +80,7 @@ describe('ItemMyDSpaceResultListElementComponent', () => { const actionPayload: any = { reloadedObject: {}}; const actionsComponent = fixture.debugElement.query(By.css('ds-item-actions')); - actionsComponent.triggerEventHandler('processCompleted', actionPayload) + 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.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 a474d866cd..e55b45aed7 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,4 +1,4 @@ -import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core'; +import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { waitForAsync, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; @@ -103,7 +103,7 @@ describe('PoolSearchResultListElementComponent', () => { 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) + 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/workflow-item-search-result/workflow-item-search-result-list-element.component.spec.ts b/src/app/shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component.spec.ts index 1de3f42264..3743a9bd22 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component.spec.ts @@ -101,7 +101,7 @@ describe('WorkflowItemSearchResultListElementComponent', () => { const actionPayload: any = { reloadedObject: {}}; const actionsComponent = fixture.debugElement.query(By.css('ds-workflowitem-actions')); - actionsComponent.triggerEventHandler('processCompleted', actionPayload) + 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.spec.ts b/src/app/shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component.spec.ts index ee1be01fe4..b1f2a2aeb9 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component.spec.ts @@ -101,7 +101,7 @@ describe('WorkspaceItemSearchResultListElementComponent', () => { const actionPayload: any = { reloadedObject: {}}; const actionsComponent = fixture.debugElement.query(By.css('ds-workspaceitem-actions')); - actionsComponent.triggerEventHandler('processCompleted', actionPayload) + actionsComponent.triggerEventHandler('processCompleted', actionPayload); tick(); expect(component.reloadedObject.emit).toHaveBeenCalledWith(actionPayload.reloadedObject); From 5f6ad03f6f73841969f2a32e80c11f0cff2e8d69 Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Tue, 9 Feb 2021 14:42:38 +0100 Subject: [PATCH 13/18] [CSTPER-3620] Workflow Actions refresh entire MyDSpace page instead of just WorkflowItem Merge cache part 2 --- src/app/core/tasks/claimed-task-data.service.ts | 6 +++++- src/app/core/tasks/pool-task-data.service.ts | 6 +++++- src/app/core/tasks/tasks.service.ts | 7 ++++++- .../claimed-task/claimed-task-actions.component.ts | 8 ++------ ...laimed-task-actions-return-to-pool.component.ts | 5 +---- .../mydspace-reloadable-actions.ts | 2 -- .../pool-task/pool-task-actions.component.ts | 6 +----- ...pproved-search-result-list-element.component.ts | 14 +++++++++++--- ...eclined-search-result-list-element.component.ts | 13 ++++++++++--- 9 files changed, 41 insertions(+), 26 deletions(-) diff --git a/src/app/core/tasks/claimed-task-data.service.ts b/src/app/core/tasks/claimed-task-data.service.ts index b9c7e9ee54..c8ab170bfe 100644 --- a/src/app/core/tasks/claimed-task-data.service.ts +++ b/src/app/core/tasks/claimed-task-data.service.ts @@ -119,7 +119,11 @@ export class ClaimedTaskDataService extends TasksService { options.searchParams = [ new RequestParam('uuid', uuid) ]; - return this.searchTask('findByItem', options, followLink('workflowitem')) + return this.searchTask('findByItem', options, followLink('workflowitem', + null, + true, + false, + true)) .pipe(getFirstSucceededRemoteData()); } diff --git a/src/app/core/tasks/pool-task-data.service.ts b/src/app/core/tasks/pool-task-data.service.ts index 26eb3e6dad..83aad10fe5 100644 --- a/src/app/core/tasks/pool-task-data.service.ts +++ b/src/app/core/tasks/pool-task-data.service.ts @@ -71,7 +71,11 @@ export class PoolTaskDataService extends TasksService { options.searchParams = [ new RequestParam('uuid', uuid) ]; - return this.searchTask('findByItem', options, followLink('workflowitem')) + return this.searchTask('findByItem', options, followLink('workflowitem', + null, + true, + false, + true)) .pipe(getFirstSucceededRemoteData()); } diff --git a/src/app/core/tasks/tasks.service.ts b/src/app/core/tasks/tasks.service.ts index ddc262d9b2..851112aa42 100644 --- a/src/app/core/tasks/tasks.service.ts +++ b/src/app/core/tasks/tasks.service.ts @@ -131,14 +131,19 @@ export abstract class TasksService extends DataServic * links to follow */ public searchTask(searchMethod: string, options: FindListOptions = {}, ...linksToFollow: FollowLinkConfig[]): Observable> { + const correlationId = Math.floor(Math.random() * 1000); const hrefObs = this.getSearchByHref(searchMethod, options, ...linksToFollow); return hrefObs.pipe( + tap(href => console.log('CorrelationId: ' + correlationId, 'SearchTaskHref', href)), find((href: string) => hasValue(href)), - switchMap((href) => this.findByHref(href).pipe( + switchMap((href) => this.findByHref(href, false, true).pipe( getFirstSucceededRemoteData(), tap(() => { this.requestService.setStaleByHrefSubstring(searchMethod); }))), + tap((response) => { + console.log('CorrelationId: ' + correlationId, 'SearchTaskResponse', response.payload); + }) ); } } diff --git a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts index afb50c8351..b610232279 100644 --- a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts @@ -1,4 +1,4 @@ -import {Component, Injector, Input, OnDestroy, OnInit} from '@angular/core'; +import { Component, Injector, Input, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { Observable } from 'rxjs'; @@ -26,7 +26,7 @@ import { WORKFLOW_TASK_OPTION_RETURN_TO_POOL } from './return-to-pool/claimed-ta styleUrls: ['./claimed-task-actions.component.scss'], templateUrl: './claimed-task-actions.component.html', }) -export class ClaimedTaskActionsComponent extends MyDSpaceActionsComponent implements OnInit, OnDestroy { +export class ClaimedTaskActionsComponent extends MyDSpaceActionsComponent implements OnInit { /** * The ClaimedTask object @@ -100,8 +100,4 @@ export class ClaimedTaskActionsComponent extends MyDSpaceActionsComponent | DSpaceObject> { - return this.poolTaskService.findByItem(this.itemUuid).pipe(take(1), tap((value) => { - console.log('The new PoolTask (found by item) is:', value); - })); + return this.poolTaskService.findByItem(this.itemUuid).pipe(take(1)); } actionExecution(): Observable { diff --git a/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts b/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts index 2a9a14a341..c24d493d6b 100644 --- a/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts +++ b/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts @@ -53,7 +53,6 @@ export abstract class MyDSpaceReloadableActionsComponent { this.processing$.next(false); this.handleReloadableActionResponse(res.hasSucceeded, reloadedObject); - return reloadedObject; }) ); } else { 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 19f257f512..92086ac817 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 @@ -19,7 +19,6 @@ 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'; -import { tap } from 'rxjs/internal/operators/tap'; /** * This component represents mydspace actions related to PoolTask object. @@ -96,9 +95,7 @@ export class PoolTaskActionsComponent extends MyDSpaceReloadableActionsComponent } reloadObjectExecution(): Observable | DSpaceObject> { - return this.claimedTaskService.findByItem(this.itemUuid).pipe(take(1), tap((value) => { - console.log('The new ClaimTask (found by item) is:', value); - })); + return this.claimedTaskService.findByItem(this.itemUuid).pipe(take(1)); } /** @@ -116,7 +113,6 @@ export class PoolTaskActionsComponent extends MyDSpaceReloadableActionsComponent ngOnDestroy() { this.subs.forEach((sub) => sub.unsubscribe()); - console.log('Destroy of PoolTaskActionsComponent', this.object); } } 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 index 9eb538ab6a..5423722160 100644 --- 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 @@ -51,9 +51,17 @@ export class ClaimedApprovedSearchResultListElementComponent extends SearchResul */ ngOnInit() { super.ngOnInit(); - this.linkService.resolveLinks(this.dso, followLink('workflowitem', null, true, - followLink('item'), followLink('submitter') - ), followLink('action')); + this.linkService.resolveLinks(this.dso, + followLink('workflowitem', + null, + true, + false, + 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-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 index ee90953c8c..7db12c1725 100644 --- 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 @@ -52,9 +52,16 @@ export class ClaimedDeclinedSearchResultListElementComponent extends SearchResul */ ngOnInit() { super.ngOnInit(); - this.linkService.resolveLinks(this.dso, followLink('workflowitem', null, true, - followLink('item'), followLink('submitter') - ), followLink('action')); + this.linkService.resolveLinks(this.dso, + followLink('workflowitem', + null, + true, + false, + true, + followLink('item'), + followLink('submitter') + ), + followLink('action')); this.workflowitemRD$ = this.dso.workflowitem as Observable>; } From d015b6caefa06dfc72ce649e6f716cf1ea2c9095 Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Tue, 9 Feb 2021 16:19:07 +0100 Subject: [PATCH 14/18] [CSTPER-3620] Workflow Actions refresh entire MyDSpace page instead of just WorkflowItem Test fixes --- src/app/core/tasks/claimed-task-data.service.spec.ts | 11 +++++++++-- src/app/core/tasks/pool-task-data.service.spec.ts | 10 ++++++++-- src/app/core/tasks/tasks.service.spec.ts | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) 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 b9f38706e1..977590fbed 100644 --- a/src/app/core/tasks/claimed-task-data.service.spec.ts +++ b/src/app/core/tasks/claimed-task-data.service.spec.ts @@ -15,6 +15,7 @@ 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'; +import {createSuccessfulRemoteDataObject$} from '../../shared/remote-data.utils'; describe('ClaimedTaskDataService', () => { let scheduler: TestScheduler; @@ -109,7 +110,7 @@ describe('ClaimedTaskDataService', () => { describe('findByItem', () => { it('should call searchTask method', () => { - spyOn(service, 'searchTask').and.returnValue(observableOf(null)); + spyOn((service as any), 'searchTask').and.returnValue(observableOf(createSuccessfulRemoteDataObject$({}))); scheduler.schedule(() => service.findByItem('a0db0fde-1d12-4d43-bd0d-0f43df8d823c').subscribe()); scheduler.flush(); @@ -119,7 +120,13 @@ describe('ClaimedTaskDataService', () => { new RequestParam('uuid', 'a0db0fde-1d12-4d43-bd0d-0f43df8d823c') ]; - expect(service.searchTask).toHaveBeenCalledWith('findByItem', findListOptions, followLink('workflowitem')); + expect(service.searchTask).toHaveBeenCalledWith('findByItem', + findListOptions, + followLink('workflowitem', + null, + true, + false, + true)); }); }); }); 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 98461864a9..07fa5a9d66 100644 --- a/src/app/core/tasks/pool-task-data.service.spec.ts +++ b/src/app/core/tasks/pool-task-data.service.spec.ts @@ -15,6 +15,7 @@ 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'; +import {createSuccessfulRemoteDataObject$} from '../../shared/remote-data.utils'; describe('PoolTaskDataService', () => { let scheduler: TestScheduler; @@ -63,7 +64,7 @@ describe('PoolTaskDataService', () => { describe('findByItem', () => { it('should call searchTask method', () => { - spyOn(service, 'searchTask').and.returnValue(observableOf(null)); + spyOn((service as any), 'searchTask').and.returnValue(observableOf(createSuccessfulRemoteDataObject$({}))); scheduler.schedule(() => service.findByItem('a0db0fde-1d12-4d43-bd0d-0f43df8d823c').subscribe()); scheduler.flush(); @@ -73,7 +74,12 @@ describe('PoolTaskDataService', () => { new RequestParam('uuid', 'a0db0fde-1d12-4d43-bd0d-0f43df8d823c') ]; - expect(service.searchTask).toHaveBeenCalledWith('findByItem', findListOptions, followLink('workflowitem')); + expect(service.searchTask).toHaveBeenCalledWith('findByItem', findListOptions, + followLink('workflowitem', + null, + true, + false, + true)); }); }); diff --git a/src/app/core/tasks/tasks.service.spec.ts b/src/app/core/tasks/tasks.service.spec.ts index 619b9f263a..f0c86d2abf 100644 --- a/src/app/core/tasks/tasks.service.spec.ts +++ b/src/app/core/tasks/tasks.service.spec.ts @@ -136,7 +136,7 @@ describe('TasksService', () => { scheduler.flush(); expect(service.getSearchByHref).toHaveBeenCalledWith('method', options, followLinks as any); - expect(service.findByHref).toHaveBeenCalledWith('generatedHref'); + expect(service.findByHref).toHaveBeenCalledWith('generatedHref', false, true); }); }); From 8b57610be4ab38685b43f4a2a806876a8bc50fa2 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 19 Feb 2021 12:09:12 +0100 Subject: [PATCH 15/18] [CST-3620] Add component destroy on reload of loaded object within the listable-object-component-loader.component.ts --- .../listable-object-component-loader.component.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 9fc27d65bf..ff85c59885 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, OnDestroy, OnInit, ViewChild, ElementRef } from '@angular/core'; +import { Component, ComponentFactoryResolver, ElementRef, 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'; @@ -9,6 +9,7 @@ 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'; +import { take } from 'rxjs/operators'; @Component({ selector: 'ds-listable-object-component-loader', @@ -135,13 +136,14 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnDestroy (componentRef.instance as any).value = this.value; if ((componentRef.instance as any).reloadedObject) { - this.subs.push((componentRef.instance as any).reloadedObject.subscribe((reloadedObject: DSpaceObject) => { + (componentRef.instance as any).reloadedObject.pipe(take(1)).subscribe((reloadedObject: DSpaceObject) => { if (reloadedObject) { + componentRef.destroy(); console.log('Reloaded Object from/to', this.object, reloadedObject); this.object = reloadedObject; this.instantiateComponent(reloadedObject); } - })); + }); } } From 27905b2038e937d4bf1640288204a74de23438fb Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 19 Feb 2021 13:08:38 +0100 Subject: [PATCH 16/18] [CST-3620] Fix issue with TasksService findByItem method that returns stale response when second request is made --- src/app/core/tasks/tasks.service.ts | 89 +++++++++++++++-------------- 1 file changed, 45 insertions(+), 44 deletions(-) diff --git a/src/app/core/tasks/tasks.service.ts b/src/app/core/tasks/tasks.service.ts index 851112aa42..f23c71e65e 100644 --- a/src/app/core/tasks/tasks.service.ts +++ b/src/app/core/tasks/tasks.service.ts @@ -1,14 +1,20 @@ import { HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs'; -import { distinctUntilChanged, filter, find, map, mergeMap, switchMap, tap } from 'rxjs/operators'; +import { distinctUntilChanged, filter, find, map, mergeMap, tap } from 'rxjs/operators'; import { DataService } from '../data/data.service'; -import { DeleteRequest, FindListOptions, PostRequest, TaskDeleteRequest, TaskPostRequest } from '../data/request.models'; +import { + DeleteRequest, + FindListOptions, + PostRequest, + TaskDeleteRequest, + TaskPostRequest +} from '../data/request.models'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { HttpOptions } from '../dspace-rest/dspace-rest.service'; import { ProcessTaskResponse } from './models/process-task-response'; -import { getFirstCompletedRemoteData, getFirstSucceededRemoteData } from '../shared/operators'; +import { getAllCompletedRemoteData, getFirstCompletedRemoteData } from '../shared/operators'; import { CacheableObject } from '../cache/object-cache.reducer'; import { RemoteData } from '../data/remote-data'; import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; @@ -18,27 +24,6 @@ import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; */ export abstract class TasksService extends DataService { - /** - * Fetch a RestRequest - * - * @param requestId - * The base endpoint for the type of object - * @return Observable - * server response - */ - protected fetchRequest(requestId: string): Observable { - return this.rdbService.buildFromRequestUUID(requestId).pipe( - getFirstCompletedRemoteData(), - map((response: RemoteData) => { - if (response.hasFailed) { - return new ProcessTaskResponse(false, response.statusCode, response.errorMessage); - } else { - return new ProcessTaskResponse(true, response.statusCode); - } - }) - ); - } - /** * Create the HREF for a specific submission object based on its identifier * @@ -98,17 +83,6 @@ export abstract class TasksService extends DataServic distinctUntilChanged()); } - /** - * Create a new HttpOptions - */ - protected makeHttpOptions() { - const options: HttpOptions = Object.create({}); - let headers = new HttpHeaders(); - headers = headers.append('Content-Type', 'application/x-www-form-urlencoded'); - options.headers = headers; - return options; - } - /** * Get the endpoint of a task by scopeId. * @param linkPath @@ -131,19 +105,46 @@ export abstract class TasksService extends DataServic * links to follow */ public searchTask(searchMethod: string, options: FindListOptions = {}, ...linksToFollow: FollowLinkConfig[]): Observable> { - const correlationId = Math.floor(Math.random() * 1000); const hrefObs = this.getSearchByHref(searchMethod, options, ...linksToFollow); return hrefObs.pipe( - tap(href => console.log('CorrelationId: ' + correlationId, 'SearchTaskHref', href)), find((href: string) => hasValue(href)), - switchMap((href) => this.findByHref(href, false, true).pipe( - getFirstSucceededRemoteData(), - tap(() => { - this.requestService.setStaleByHrefSubstring(searchMethod); - }))), - tap((response) => { - console.log('CorrelationId: ' + correlationId, 'SearchTaskResponse', response.payload); + mergeMap((href) => this.findByHref(href, false, true).pipe( + getAllCompletedRemoteData(), + filter((rd: RemoteData) => !rd.isSuccessStale), + tap(() => this.requestService.setStaleByHrefSubstring(href))) + ) + ); + } + + /** + * Fetch a RestRequest + * + * @param requestId + * The base endpoint for the type of object + * @return Observable + * server response + */ + protected fetchRequest(requestId: string): Observable { + return this.rdbService.buildFromRequestUUID(requestId).pipe( + getFirstCompletedRemoteData(), + map((response: RemoteData) => { + if (response.hasFailed) { + return new ProcessTaskResponse(false, response.statusCode, response.errorMessage); + } else { + return new ProcessTaskResponse(true, response.statusCode); + } }) ); } + + /** + * Create a new HttpOptions + */ + protected makeHttpOptions() { + const options: HttpOptions = Object.create({}); + let headers = new HttpHeaders(); + headers = headers.append('Content-Type', 'application/x-www-form-urlencoded'); + options.headers = headers; + return options; + } } From c78cd9ad71cc361cf6f9bc6aca68ff46e614f161 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 19 Feb 2021 13:09:38 +0100 Subject: [PATCH 17/18] [CST-3620] Remove console logs --- src/app/core/tasks/claimed-task-data.service.ts | 7 ++----- .../listable-object-component-loader.component.ts | 1 - 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/app/core/tasks/claimed-task-data.service.ts b/src/app/core/tasks/claimed-task-data.service.ts index c8ab170bfe..f2d5c94585 100644 --- a/src/app/core/tasks/claimed-task-data.service.ts +++ b/src/app/core/tasks/claimed-task-data.service.ts @@ -65,12 +65,12 @@ export class ClaimedTaskDataService extends TasksService { * * @param scopeId * The task id + * @param poolTaskHref + * The pool task Href * @return {Observable} * Emit the server response */ public claimTask(scopeId: string, poolTaskHref: string): Observable { - console.log('=========================================='); - console.log('User ClaimTask request for:', scopeId, poolTaskHref); const options: HttpOptions = Object.create({}); let headers = new HttpHeaders(); headers = headers.append('Content-Type', 'text/uri-list'); @@ -101,8 +101,6 @@ export class ClaimedTaskDataService extends TasksService { * Emit the server response */ public returnToPoolTask(scopeId: string): Observable { - console.log('=========================================='); - console.log('User ReturnToPool request for:', scopeId); return this.deleteById(this.linkPath, scopeId, this.makeHttpOptions()); } @@ -114,7 +112,6 @@ export class ClaimedTaskDataService extends TasksService { * The server response */ public findByItem(uuid: string): Observable> { - console.log('claimedTaskService findByItem', uuid); const options = new FindListOptions(); options.searchParams = [ new RequestParam('uuid', uuid) 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 ff85c59885..fb58c2c83d 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 @@ -139,7 +139,6 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnDestroy (componentRef.instance as any).reloadedObject.pipe(take(1)).subscribe((reloadedObject: DSpaceObject) => { if (reloadedObject) { componentRef.destroy(); - console.log('Reloaded Object from/to', this.object, reloadedObject); this.object = reloadedObject; this.instantiateComponent(reloadedObject); } From 523b7a497cbac5a08cf8dae9fcf85051d5d2648b Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Mon, 22 Feb 2021 16:56:29 +0100 Subject: [PATCH 18/18] [CSTPER-3620] Fixed workspace followlink and search filters update --- .../my-dspace-page.component.html | 5 ++- .../my-dspace-page.component.ts | 16 ++++++++- .../my-dspace-results.component.html | 3 +- .../my-dspace-results.component.ts | 8 ++++- .../tasks/claimed-task-data.service.spec.ts | 11 ++---- .../core/tasks/claimed-task-data.service.ts | 8 +---- .../core/tasks/pool-task-data.service.spec.ts | 10 ++---- src/app/core/tasks/pool-task-data.service.ts | 10 ++---- .../mydspace-reloadable-actions.ts | 5 +-- .../object-collection.component.html | 1 + .../object-collection.component.ts | 5 +++ ...table-object-component-loader.component.ts | 18 +++++++++- .../object-list/object-list.component.html | 4 ++- .../object-list/object-list.component.ts | 5 +++ .../search-filters.component.spec.ts | 24 ++++++++++++- .../search-filters.component.ts | 36 ++++++++++++++++--- .../search-sidebar.component.html | 2 +- .../search-sidebar.component.ts | 6 ++++ 18 files changed, 130 insertions(+), 47 deletions(-) diff --git a/src/app/+my-dspace-page/my-dspace-page.component.html b/src/app/+my-dspace-page/my-dspace-page.component.html index 6f1cc41a1e..55d1e304d0 100644 --- a/src/app/+my-dspace-page/my-dspace-page.component.html +++ b/src/app/+my-dspace-page/my-dspace-page.component.html @@ -6,6 +6,7 @@ [configurationList]="(configurationList$ | async)" [resultCount]="(resultsRD$ | async)?.payload.totalElements" [viewModeList]="viewModeList" + [refreshFilters]="refreshFilters.asObservable()" [inPlaceSearch]="inPlaceSearch">
@@ -39,7 +41,8 @@
+ [context]="context$ | async" + (contentChange)="onResultsContentChange()">
diff --git a/src/app/+my-dspace-page/my-dspace-page.component.ts b/src/app/+my-dspace-page/my-dspace-page.component.ts index 0f08795cdc..5ee2a47d9f 100644 --- a/src/app/+my-dspace-page/my-dspace-page.component.ts +++ b/src/app/+my-dspace-page/my-dspace-page.component.ts @@ -7,7 +7,7 @@ import { OnInit } from '@angular/core'; -import { BehaviorSubject, Observable, Subscription } from 'rxjs'; +import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs'; import { map, switchMap, tap, } from 'rxjs/operators'; import { PaginatedList } from '../core/data/paginated-list.model'; @@ -101,6 +101,11 @@ export class MyDSpacePageComponent implements OnInit { */ context$: Observable; + /** + * Emit an event every time search sidebars must refresh their contents. + */ + refreshFilters: Subject = new Subject(); + constructor(private service: SearchService, private sidebarService: SidebarService, private windowService: HostWindowService, @@ -148,6 +153,14 @@ export class MyDSpacePageComponent implements OnInit { } + /** + * Handle the contentChange event from within the my dspace content. + * Notify search sidebars to refresh their content. + */ + onResultsContentChange() { + this.refreshFilters.next(); + } + /** * Set the sidebar to a collapsed state */ @@ -184,5 +197,6 @@ export class MyDSpacePageComponent implements OnInit { if (hasValue(this.sub)) { this.sub.unsubscribe(); } + this.refreshFilters.complete(); } } diff --git a/src/app/+my-dspace-page/my-dspace-results/my-dspace-results.component.html b/src/app/+my-dspace-page/my-dspace-results/my-dspace-results.component.html index 3a829e6ece..2710285f0d 100644 --- a/src/app/+my-dspace-page/my-dspace-results/my-dspace-results.component.html +++ b/src/app/+my-dspace-page/my-dspace-results/my-dspace-results.component.html @@ -5,7 +5,8 @@ [sortConfig]="searchConfig.sort" [objects]="searchResults" [hideGear]="true" - [context]="context"> + [context]="context" + (contentChange)="contentChange.emit()"> diff --git a/src/app/+my-dspace-page/my-dspace-results/my-dspace-results.component.ts b/src/app/+my-dspace-page/my-dspace-results/my-dspace-results.component.ts index 35b13c8bae..32b6d9c9f7 100644 --- a/src/app/+my-dspace-page/my-dspace-results/my-dspace-results.component.ts +++ b/src/app/+my-dspace-page/my-dspace-results/my-dspace-results.component.ts @@ -1,4 +1,4 @@ -import { Component, Input } from '@angular/core'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; import { RemoteData } from '../../core/data/remote-data'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { fadeIn, fadeInOut } from '../../shared/animations/fade'; @@ -41,6 +41,12 @@ export class MyDSpaceResultsComponent { * The current context for the search results */ @Input() context: Context; + + /** + * Emit when one of the results has changed. + */ + @Output() contentChange = new EventEmitter(); + /** * A boolean representing if search results entry are separated by a line */ 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 977590fbed..ab9727592e 100644 --- a/src/app/core/tasks/claimed-task-data.service.spec.ts +++ b/src/app/core/tasks/claimed-task-data.service.spec.ts @@ -11,11 +11,10 @@ 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'; -import {createSuccessfulRemoteDataObject$} from '../../shared/remote-data.utils'; +import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; describe('ClaimedTaskDataService', () => { let scheduler: TestScheduler; @@ -120,13 +119,7 @@ describe('ClaimedTaskDataService', () => { new RequestParam('uuid', 'a0db0fde-1d12-4d43-bd0d-0f43df8d823c') ]; - expect(service.searchTask).toHaveBeenCalledWith('findByItem', - findListOptions, - followLink('workflowitem', - null, - true, - false, - true)); + expect(service.searchTask).toHaveBeenCalledWith('findByItem', findListOptions); }); }); }); diff --git a/src/app/core/tasks/claimed-task-data.service.ts b/src/app/core/tasks/claimed-task-data.service.ts index f2d5c94585..9cfd5a44d6 100644 --- a/src/app/core/tasks/claimed-task-data.service.ts +++ b/src/app/core/tasks/claimed-task-data.service.ts @@ -16,7 +16,6 @@ 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'; @@ -116,12 +115,7 @@ export class ClaimedTaskDataService extends TasksService { options.searchParams = [ new RequestParam('uuid', uuid) ]; - return this.searchTask('findByItem', options, followLink('workflowitem', - null, - true, - false, - true)) - .pipe(getFirstSucceededRemoteData()); + return this.searchTask('findByItem', options).pipe(getFirstSucceededRemoteData()); } } 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 07fa5a9d66..7279c96e5c 100644 --- a/src/app/core/tasks/pool-task-data.service.spec.ts +++ b/src/app/core/tasks/pool-task-data.service.spec.ts @@ -11,11 +11,10 @@ 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'; -import {createSuccessfulRemoteDataObject$} from '../../shared/remote-data.utils'; +import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; describe('PoolTaskDataService', () => { let scheduler: TestScheduler; @@ -74,12 +73,7 @@ describe('PoolTaskDataService', () => { new RequestParam('uuid', 'a0db0fde-1d12-4d43-bd0d-0f43df8d823c') ]; - expect(service.searchTask).toHaveBeenCalledWith('findByItem', findListOptions, - followLink('workflowitem', - null, - true, - false, - true)); + expect(service.searchTask).toHaveBeenCalledWith('findByItem', findListOptions); }); }); diff --git a/src/app/core/tasks/pool-task-data.service.ts b/src/app/core/tasks/pool-task-data.service.ts index 83aad10fe5..d44e402e7f 100644 --- a/src/app/core/tasks/pool-task-data.service.ts +++ b/src/app/core/tasks/pool-task-data.service.ts @@ -15,10 +15,9 @@ import { PoolTask } from './models/pool-task-object.model'; import { POOL_TASK } from './models/pool-task-object.resource-type'; 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 { getFirstSucceededRemoteData } from '../shared/operators'; +import { getFirstCompletedRemoteData } from '../shared/operators'; /** * The service handling all REST requests for PoolTask @@ -71,12 +70,7 @@ export class PoolTaskDataService extends TasksService { options.searchParams = [ new RequestParam('uuid', uuid) ]; - return this.searchTask('findByItem', options, followLink('workflowitem', - null, - true, - false, - true)) - .pipe(getFirstSucceededRemoteData()); + return this.searchTask('findByItem', options).pipe(getFirstCompletedRemoteData()); } /** diff --git a/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts b/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts index c24d493d6b..7043191915 100644 --- a/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts +++ b/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts @@ -1,7 +1,7 @@ import { Router } from '@angular/router'; import { Component, Injector, OnInit } from '@angular/core'; -import { map, switchMap, tap} from 'rxjs/operators'; +import { map, switchMap, take, tap } from 'rxjs/operators'; import { RemoteData } from '../../core/data/remote-data'; import { DataService } from '../../core/data/data.service'; @@ -64,6 +64,7 @@ export abstract class MyDSpaceReloadableActionsComponent { this.processing$.next(true); return this.actionExecution().pipe( + take(1), switchMap((res: ProcessTaskResponse) => { if (res.hasSucceeded) { return this._reloadObject().pipe( @@ -137,7 +138,7 @@ export abstract class MyDSpaceReloadableActionsComponent { - return dso ? this.convertReloadedObject(dso) : dso; + return dso ? this.convertReloadedObject(dso) : dso; })); } diff --git a/src/app/shared/object-collection/object-collection.component.html b/src/app/shared/object-collection/object-collection.component.html index e696170a6f..f2778757ef 100644 --- a/src/app/shared/object-collection/object-collection.component.html +++ b/src/app/shared/object-collection/object-collection.component.html @@ -18,6 +18,7 @@ [importable]="importable" [importConfig]="importConfig" (importObject)="importObject.emit($event)" + (contentChange)="contentChange.emit()" *ngIf="(currentMode$ | async) === viewModeEnum.ListElement"> diff --git a/src/app/shared/object-collection/object-collection.component.ts b/src/app/shared/object-collection/object-collection.component.ts index ffb5c42880..52881f5eaf 100644 --- a/src/app/shared/object-collection/object-collection.component.ts +++ b/src/app/shared/object-collection/object-collection.component.ts @@ -53,6 +53,11 @@ export class ObjectCollectionComponent implements OnInit { @Output() deselectObject: EventEmitter = new EventEmitter(); @Output() selectObject: EventEmitter = new EventEmitter(); + /** + * Emit when one of the collection's object has changed. + */ + @Output() contentChange = new EventEmitter(); + /** * Whether or not to add an import button to the object elements */ 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 fb58c2c83d..51468993c1 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,14 @@ -import { Component, ComponentFactoryResolver, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { + Component, + ComponentFactoryResolver, + ElementRef, + Input, + OnDestroy, + OnInit, + Output, + ViewChild, + EventEmitter +} from '@angular/core'; import { ListableObject } from '../listable-object.model'; import { ViewMode } from '../../../../core/shared/view-mode.model'; import { Context } from '../../../../core/shared/context.model'; @@ -76,6 +86,11 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnDestroy */ @ViewChild('badges', { static: true }) badges: ElementRef; + /** + * Emit when the listable object has been reloaded. + */ + @Output() contentChange = new EventEmitter(); + /** * Whether or not the "Private" badge should be displayed for this listable object */ @@ -141,6 +156,7 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnDestroy componentRef.destroy(); this.object = reloadedObject; this.instantiateComponent(reloadedObject); + this.contentChange.emit(reloadedObject); } }); } diff --git a/src/app/shared/object-list/object-list.component.html b/src/app/shared/object-list/object-list.component.html index 4aecaaac8f..331ff1cb28 100644 --- a/src/app/shared/object-list/object-list.component.html +++ b/src/app/shared/object-list/object-list.component.html @@ -22,7 +22,9 @@ [importConfig]="importConfig" (importObject)="importObject.emit($event)"> + [listID]="selectionConfig?.listId" + (contentChange)="contentChange.emit()" + > diff --git a/src/app/shared/object-list/object-list.component.ts b/src/app/shared/object-list/object-list.component.ts index b58c8b358e..6f4caae939 100644 --- a/src/app/shared/object-list/object-list.component.ts +++ b/src/app/shared/object-list/object-list.component.ts @@ -76,6 +76,11 @@ export class ObjectListComponent { */ @Input() importConfig: { importLabel: string }; + /** + * Emit when one of the listed object has changed. + */ + @Output() contentChange = new EventEmitter(); + /** * The current listable objects */ diff --git a/src/app/shared/search/search-filters/search-filters.component.spec.ts b/src/app/shared/search/search-filters/search-filters.component.spec.ts index aaea82df27..2dd810db63 100644 --- a/src/app/shared/search/search-filters/search-filters.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filters.component.spec.ts @@ -7,7 +7,7 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { SearchFilterService } from '../../../core/shared/search/search-filter.service'; import { SearchFiltersComponent } from './search-filters.component'; import { SearchService } from '../../../core/shared/search/search.service'; -import { of as observableOf } from 'rxjs'; +import { of as observableOf, Subject } from 'rxjs'; import { SEARCH_CONFIG_SERVICE } from '../../../+my-dspace-page/my-dspace-page.component'; import { SearchConfigurationServiceStub } from '../../testing/search-configuration-service.stub'; @@ -66,4 +66,26 @@ describe('SearchFiltersComponent', () => { }); }); + describe('when refreshSearch observable is present and emit events', () => { + + let refreshFiltersEmitter: Subject; + + beforeEach(() => { + spyOn(comp, 'initFilters').and.callFake(() => { /****/}); + + refreshFiltersEmitter = new Subject(); + comp.refreshFilters = refreshFiltersEmitter.asObservable(); + comp.ngOnInit(); + }); + + it('should reinitialize search filters', () => { + + expect(comp.initFilters).toHaveBeenCalledTimes(1); + + refreshFiltersEmitter.next(); + + expect(comp.initFilters).toHaveBeenCalledTimes(2); + }); + }); + }); diff --git a/src/app/shared/search/search-filters/search-filters.component.ts b/src/app/shared/search/search-filters/search-filters.component.ts index 5daa0f17e0..348af6743d 100644 --- a/src/app/shared/search/search-filters/search-filters.component.ts +++ b/src/app/shared/search/search-filters/search-filters.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject, Input, OnInit } from '@angular/core'; +import { Component, Inject, Input, OnDestroy, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; @@ -12,17 +12,19 @@ import { getFirstSucceededRemoteData } from '../../../core/shared/operators'; import { SEARCH_CONFIG_SERVICE } from '../../../+my-dspace-page/my-dspace-page.component'; import { currentPath } from '../../utils/route.utils'; import { Router } from '@angular/router'; +import { hasValue } from '../../empty.util'; @Component({ selector: 'ds-search-filters', styleUrls: ['./search-filters.component.scss'], templateUrl: './search-filters.component.html', + }) /** * This component represents the part of the search sidebar that contains filters. */ -export class SearchFiltersComponent implements OnInit { +export class SearchFiltersComponent implements OnInit, OnDestroy { /** * An observable containing configuration about which filters are shown and how they are shown */ @@ -39,11 +41,18 @@ export class SearchFiltersComponent implements OnInit { */ @Input() inPlaceSearch; + /** + * Emits when the search filters values may be stale, and so they must be refreshed. + */ + @Input() refreshFilters: Observable; + /** * Link to the search page */ searchLink: string; + subs = []; + /** * Initialize instance variables * @param {SearchService} searchService @@ -58,9 +67,12 @@ export class SearchFiltersComponent implements OnInit { } ngOnInit(): void { - this.filters = this.searchConfigService.searchOptions.pipe( - switchMap((options) => this.searchService.getConfig(options.scope, options.configuration).pipe(getFirstSucceededRemoteData())), - ); + + this.initFilters(); + + if (this.refreshFilters) { + this.subs.push(this.refreshFilters.subscribe(() => this.initFilters())); + } this.clearParams = this.searchConfigService.getCurrentFrontendFilters().pipe(map((filters) => { Object.keys(filters).forEach((f) => filters[f] = null); @@ -69,6 +81,12 @@ export class SearchFiltersComponent implements OnInit { this.searchLink = this.getSearchLink(); } + initFilters() { + this.filters = this.searchConfigService.searchOptions.pipe( + switchMap((options) => this.searchService.getConfig(options.scope, options.configuration).pipe(getFirstSucceededRemoteData())), + ); + } + /** * @returns {string} The base path to the search page, or the current page when inPlaceSearch is true */ @@ -85,4 +103,12 @@ export class SearchFiltersComponent implements OnInit { trackUpdate(index, config: SearchFilterConfig) { return config ? config.name : undefined; } + + ngOnDestroy() { + this.subs.forEach((sub) => { + if (hasValue(sub)) { + sub.unsubscribe(); + } + }); + } } diff --git a/src/app/shared/search/search-sidebar/search-sidebar.component.html b/src/app/shared/search/search-sidebar/search-sidebar.component.html index 638aed7834..74abeadfd8 100644 --- a/src/app/shared/search/search-sidebar/search-sidebar.component.html +++ b/src/app/shared/search/search-sidebar/search-sidebar.component.html @@ -11,7 +11,7 @@ diff --git a/src/app/shared/search/search-sidebar/search-sidebar.component.ts b/src/app/shared/search/search-sidebar/search-sidebar.component.ts index 42e8a444bc..2060e0f345 100644 --- a/src/app/shared/search/search-sidebar/search-sidebar.component.ts +++ b/src/app/shared/search/search-sidebar/search-sidebar.component.ts @@ -1,6 +1,7 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { SearchConfigurationOption } from '../search-switch-configuration/search-configuration-option.model'; +import { Observable } from 'rxjs'; /** * This component renders a simple item page. @@ -44,6 +45,11 @@ export class SearchSidebarComponent { */ @Input() inPlaceSearch; + /** + * Emits when the search filters values may be stale, and so they must be refreshed. + */ + @Input() refreshFilters: Observable; + /** * Emits event when the user clicks a button to open or close the sidebar */