diff --git a/src/app/+my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.spec.ts b/src/app/+my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.spec.ts
index 4ff31d67cf..7c6d8918cb 100644
--- a/src/app/+my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.spec.ts
+++ b/src/app/+my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.spec.ts
@@ -21,6 +21,10 @@ import { UploaderService } from '../../shared/uploader/uploader.service';
import { HostWindowService } from '../../shared/host-window.service';
import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub';
import { UploaderComponent } from '../../shared/uploader/uploader.component';
+import { HttpXsrfTokenExtractor } from '@angular/common/http';
+import { CookieService } from '../../core/services/cookie.service';
+import { CookieServiceMock } from '../../shared/mocks/cookie.service.mock';
+import { HttpXsrfTokenExtractorMock } from '../../shared/mocks/http-xsrf-token-extractor.mock';
describe('MyDSpaceNewSubmissionComponent test', () => {
@@ -55,6 +59,8 @@ describe('MyDSpaceNewSubmissionComponent test', () => {
ChangeDetectorRef,
MyDSpaceNewSubmissionComponent,
UploaderService,
+ { provide: HttpXsrfTokenExtractor, useValue: new HttpXsrfTokenExtractorMock('mock-token') },
+ { provide: CookieService, useValue: new CookieServiceMock() },
{ provide: HostWindowService, useValue: new HostWindowServiceStub(800) },
],
schemas: [NO_ERRORS_SCHEMA]
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/+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/claimed-task-data.service.spec.ts b/src/app/core/tasks/claimed-task-data.service.spec.ts
index 98a0f5f51e..ab9727592e 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 { 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;
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 as any), 'searchTask').and.returnValue(observableOf(createSuccessfulRemoteDataObject$({})));
+
+ 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);
+ });
+ });
});
diff --git a/src/app/core/tasks/claimed-task-data.service.ts b/src/app/core/tasks/claimed-task-data.service.ts
index 5815dad6e5..9cfd5a44d6 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 { 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
@@ -23,7 +28,7 @@ import { TasksService } from './tasks.service';
@dataService(CLAIMED_TASK)
export class ClaimedTaskDataService extends TasksService {
- protected responseMsToLive = 10 * 1000;
+ protected responseMsToLive = 1000;
/**
* The endpoint link name
@@ -54,6 +59,24 @@ export class ClaimedTaskDataService extends TasksService {
super();
}
+ /**
+ * Make a request to claim the given task
+ *
+ * @param scopeId
+ * The task id
+ * @param poolTaskHref
+ * The pool task Href
+ * @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 +103,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).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 75255d3e0a..7279c96e5c 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 { 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;
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 as any), 'searchTask').and.returnValue(observableOf(createSuccessfulRemoteDataObject$({})));
+
+ 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);
+ });
+ });
+
+ 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..d44e402e7f 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 { FindListOptions } from '../data/request.models';
+import { RequestParam } from '../cache/models/request-param.model';
+import { getFirstCompletedRemoteData } from '../shared/operators';
/**
* The service handling all REST requests for PoolTask
@@ -28,7 +31,7 @@ export class PoolTaskDataService extends TasksService {
*/
protected linkPath = 'pooltasks';
- protected responseMsToLive = 10 * 1000;
+ protected responseMsToLive = 1000;
/**
* Initialize instance variables
@@ -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).pipe(getFirstCompletedRemoteData());
}
+
+ /**
+ * 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 c6f965b79f..f0c86d2abf 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,11 @@ 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';
+import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
+import { of } from 'rxjs';
const LINK_NAME = 'test';
@@ -59,7 +62,7 @@ describe('TasksService', () => {
const requestService = getMockRequestService();
const halService: any = new HALEndpointServiceStub(taskEndpoint);
const rdbService = getMockRemoteDataBuildService();
- const notificationsService = {} as NotificationsService;
+ const notificationsService = new NotificationsServiceStub() as any;
const http = {} as HttpClient;
const comparator = new DummyChangeAnalyzer() as any;
const objectCache = {
@@ -118,4 +121,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(of(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', false, true);
+ });
+ });
+
+ 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 8f337b7bd2..f23c71e65e 100644
--- a/src/app/core/tasks/tasks.service.ts
+++ b/src/app/core/tasks/tasks.service.ts
@@ -1,43 +1,29 @@
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, 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 } 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';
/**
* An abstract class that provides methods to handle task requests.
*/
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
*
@@ -46,7 +32,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}`;
}
@@ -90,16 +76,67 @@ 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.send(request)),
mergeMap((request: DeleteRequest) => this.fetchRequest(requestId)),
distinctUntilChanged());
}
+ /**
+ * 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: FollowLinkConfig[]): Observable> {
+ const hrefObs = this.getSearchByHref(searchMethod, options, ...linksToFollow);
+ return hrefObs.pipe(
+ find((href: string) => hasValue(href)),
+ 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
*/
diff --git a/src/app/core/xsrf/xsrf.interceptor.spec.ts b/src/app/core/xsrf/xsrf.interceptor.spec.ts
index 84f10b9e13..742c4a4a45 100644
--- a/src/app/core/xsrf/xsrf.interceptor.spec.ts
+++ b/src/app/core/xsrf/xsrf.interceptor.spec.ts
@@ -1,32 +1,20 @@
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
-import { HttpHeaders, HTTP_INTERCEPTORS, HttpResponse, HttpXsrfTokenExtractor, HttpErrorResponse } from '@angular/common/http';
+import { HttpHeaders, HTTP_INTERCEPTORS, HttpXsrfTokenExtractor } from '@angular/common/http';
import { DspaceRestService } from '../dspace-rest/dspace-rest.service';
import { RestRequestMethod } from '../data/rest-request-method';
import { CookieService } from '../services/cookie.service';
import { CookieServiceMock } from '../../shared/mocks/cookie.service.mock';
import { XsrfInterceptor } from './xsrf.interceptor';
-
-/**
- * A Mock TokenExtractor which just returns whatever token it is initialized with.
- * This mock object is injected into our XsrfInterceptor, so that it always finds
- * the same fake XSRF token.
- */
-class MockTokenExtractor extends HttpXsrfTokenExtractor {
- constructor(private token: string | null) { super(); }
-
- getToken(): string | null { return this.token; }
-}
+import { HttpXsrfTokenExtractorMock } from '../../shared/mocks/http-xsrf-token-extractor.mock';
describe(`XsrfInterceptor`, () => {
let service: DspaceRestService;
let httpMock: HttpTestingController;
let cookieService: CookieService;
- // Create a MockTokenExtractor which always returns "test-token". This will
- // be used as the test HttpXsrfTokenExtractor, see below.
+ // mock XSRF token
const testToken = 'test-token';
- const mockTokenExtractor = new MockTokenExtractor(testToken);
// Mock payload/statuses are dummy content as we are not testing the results
// of any below requests. We are only testing for X-XSRF-TOKEN header.
@@ -46,7 +34,7 @@ describe(`XsrfInterceptor`, () => {
useClass: XsrfInterceptor,
multi: true,
},
- { provide: HttpXsrfTokenExtractor, useValue: mockTokenExtractor },
+ { provide: HttpXsrfTokenExtractor, useValue: new HttpXsrfTokenExtractorMock(testToken) },
{ provide: CookieService, useValue: new CookieServiceMock() }
],
});
diff --git a/src/app/core/xsrf/xsrf.interceptor.ts b/src/app/core/xsrf/xsrf.interceptor.ts
index 7b5a66f27a..0301a70994 100644
--- a/src/app/core/xsrf/xsrf.interceptor.ts
+++ b/src/app/core/xsrf/xsrf.interceptor.ts
@@ -6,6 +6,13 @@ import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
import { CookieService } from '../services/cookie.service';
import { throwError } from 'rxjs';
+// Name of XSRF header we may send in requests to backend (this is a standard name defined by Angular)
+export const XSRF_REQUEST_HEADER = 'X-XSRF-TOKEN';
+// Name of XSRF header we may receive in responses from backend
+export const XSRF_RESPONSE_HEADER = 'DSPACE-XSRF-TOKEN';
+// Name of cookie where we store the XSRF token
+export const XSRF_COOKIE = 'XSRF-TOKEN';
+
/**
* Custom Http Interceptor intercepting Http Requests & Responses to
* exchange XSRF/CSRF tokens with the backend.
@@ -43,11 +50,6 @@ export class XsrfInterceptor implements HttpInterceptor {
* @param next
*/
intercept(req: HttpRequest, next: HttpHandler): Observable> {
- // Name of XSRF header we may send in requests to backend (this is a standard name defined by Angular)
- const requestCsrfHeader = 'X-XSRF-TOKEN';
- // Name of XSRF header we may receive in responses from backend
- const responseCsrfHeader = 'DSPACE-XSRF-TOKEN';
-
// Ensure EVERY request from Angular includes "withCredentials: true".
// This allows Angular to receive & send cookies via a CORS request (to
// the backend). ONLY requests with credentials will:
@@ -71,8 +73,8 @@ export class XsrfInterceptor implements HttpInterceptor {
const token = this.tokenExtractor.getToken() as string;
// send token in request's X-XSRF-TOKEN header (anti-CSRF security) to backend
- if (token !== null && !req.headers.has(requestCsrfHeader)) {
- req = req.clone({ headers: req.headers.set(requestCsrfHeader, token) });
+ if (token !== null && !req.headers.has(XSRF_REQUEST_HEADER)) {
+ req = req.clone({ headers: req.headers.set(XSRF_REQUEST_HEADER, token) });
}
}
// Pass to next interceptor, but intercept EVERY response event as well
@@ -82,9 +84,9 @@ export class XsrfInterceptor implements HttpInterceptor {
if (response instanceof HttpResponse) {
// For every response that comes back, check for the custom
// DSPACE-XSRF-TOKEN header sent from the backend.
- if (response.headers.has(responseCsrfHeader)) {
+ if (response.headers.has(XSRF_RESPONSE_HEADER)) {
// value of header is a new XSRF token
- this.saveXsrfToken(response.headers.get(responseCsrfHeader));
+ this.saveXsrfToken(response.headers.get(XSRF_RESPONSE_HEADER));
}
}
}),
@@ -92,9 +94,9 @@ export class XsrfInterceptor implements HttpInterceptor {
if (error instanceof HttpErrorResponse) {
// For every error that comes back, also check for the custom
// DSPACE-XSRF-TOKEN header sent from the backend.
- if (error.headers.has(responseCsrfHeader)) {
+ if (error.headers.has(XSRF_RESPONSE_HEADER)) {
// value of header is a new XSRF token
- this.saveXsrfToken(error.headers.get(responseCsrfHeader));
+ this.saveXsrfToken(error.headers.get(XSRF_RESPONSE_HEADER));
}
}
// Return error response as is.
@@ -111,7 +113,7 @@ export class XsrfInterceptor implements HttpInterceptor {
// Save token value as a *new* value of our client-side XSRF-TOKEN cookie.
// This is the cookie that is parsed by Angular's tokenExtractor(),
// which we will send back in the X-XSRF-TOKEN header per Angular best practices.
- this.cookieService.remove('XSRF-TOKEN');
- this.cookieService.set('XSRF-TOKEN', token);
+ this.cookieService.remove(XSRF_COOKIE);
+ this.cookieService.set(XSRF_COOKIE, token);
}
}
diff --git a/src/app/shared/mocks/http-xsrf-token-extractor.mock.ts b/src/app/shared/mocks/http-xsrf-token-extractor.mock.ts
new file mode 100644
index 0000000000..78766a0b31
--- /dev/null
+++ b/src/app/shared/mocks/http-xsrf-token-extractor.mock.ts
@@ -0,0 +1,12 @@
+import { HttpXsrfTokenExtractor } from '@angular/common/http';
+
+/**
+ * A Mock TokenExtractor which just returns whatever token it is initialized with.
+ * This mock object is injected into our XsrfInterceptor, so that it always finds
+ * the same fake XSRF token.
+ */
+export class HttpXsrfTokenExtractorMock extends HttpXsrfTokenExtractor {
+ constructor(private token: string | null) { super(); }
+
+ getToken(): string | null { return this.token; }
+}
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 da03fbc3ad..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,8 +1,20 @@
-import { Component, EventEmitter, Input, Output } from '@angular/core';
+import { Component, Injector, OnDestroy } from '@angular/core';
import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model';
-import { BehaviorSubject } from 'rxjs';
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 { 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';
+import { MyDSpaceReloadableActionsComponent } from '../../mydspace-reloadable-actions';
/**
* Abstract component for rendering a claimed task's action
@@ -12,31 +24,39 @@ import { ProcessTaskResponse } from '../../../../core/tasks/models/process-task-
* - Optionally overwrite createBody if the request body requires more than just the option
*/
@Component({
- selector: 'ds-calim-task-action-abstract',
+ selector: 'ds-claimed-task-action-abstract',
template: ''
})
-export abstract class ClaimedTaskActionsAbstractComponent {
+export abstract class ClaimedTaskActionsAbstractComponent extends MyDSpaceReloadableActionsComponent implements OnDestroy {
+
/**
* 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;
+
+ subs = [];
+
+ 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.subs.push(this.startActionExecution().pipe(take(1)).subscribe());
}
/**
@@ -49,17 +69,36 @@ 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() {
+ if (!(this.object as any).workflowitem) {
+ return;
+ }
+ 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/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 622c0bf18d..a785e3b26d 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 { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core';
+import { async, ComponentFixture, TestBed, waitForAsync } 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(waitForAsync(() => {
+ 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,34 @@ 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();
+ });
+ });
+
+ 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 emit a successful processCompleted event', () => {
- expect(component.processCompleted.emit).toHaveBeenCalledWith(true);
+ });
+
+ 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..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
@@ -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,24 @@ 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;
+ }
+
}
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/claimed-task-actions.component.ts b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts
index c82154af09..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
@@ -2,7 +2,7 @@ import { Component, Injector, Input, 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';
@@ -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));
}
/**
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 cdbb699ad0..1a958f5a49 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,5 +1,5 @@
-import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core';
+import { waitForAsync, 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(waitForAsync(() => {
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 7f5dcc04d3..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,4 +1,4 @@
-import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
+import { ChangeDetectionStrategy, Injector, 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';
@@ -9,22 +9,39 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { ClaimedTaskActionsRejectComponent } from './claimed-task-actions-reject.component';
import { TranslateLoaderMock } from '../../../mocks/translate-loader.mock';
import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model';
-import { of as observableOf } from 'rxjs';
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', {
- submitTask: observableOf(new ProcessTaskResponse(true))
+ submitTask: of(new ProcessTaskResponse(true))
});
beforeEach(waitForAsync(() => {
+ mockPoolTaskDataService = new PoolTaskDataService(null, null, null, null, null, null, null, null);
TestBed.configureTestingModule({
imports: [
NgbModule,
@@ -39,6 +56,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 +78,7 @@ describe('ClaimedTaskActionsRejectComponent', () => {
modalService = TestBed.inject(NgbModal);
component.object = object;
component.modalRef = modalService.open('ok');
+ spyOn(component, 'initReloadAnchor').and.returnValue(undefined);
fixture.detectChanges();
});
@@ -96,6 +120,7 @@ describe('ClaimedTaskActionsRejectComponent', () => {
beforeEach(() => {
spyOn(component.processCompleted, 'emit');
+ spyOn(component, 'startActionExecution').and.returnValue(of(null));
expectedBody = {
[component.option]: 'true',
@@ -113,12 +138,48 @@ 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', () => {
+
+ let expectedBody;
+
+ beforeEach(() => {
+ spyOn((component.rejectForm as any), 'get').and.returnValue({value: 'required'});
+ expectedBody = {
+ [component.option]: 'true',
+ reason: 'required'
+ };
+ });
+
+ it('should call claimedTaskService\'s submitTask with the proper reason', (done) => {
+ component.actionExecution().subscribe(() => {
+ expect(claimedTaskService.submitTask).toHaveBeenCalledWith(object.id, expectedBody);
+ done();
+ });
});
});
+
+ 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..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
@@ -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,15 @@ 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;
+ }
}
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 76155e1903..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,25 +1,41 @@
-import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core';
+import { ComponentFixture, fakeAsync, TestBed, waitForAsync } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { ClaimedTaskActionsReturnToPoolComponent } from './claimed-task-actions-return-to-pool.component';
import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model';
-import { of as observableOf } from 'rxjs';
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', {
- returnToPoolTask: observableOf(new ProcessTaskResponse(true))
+ returnToPoolTask: of(new ProcessTaskResponse(true))
});
beforeEach(waitForAsync(() => {
+ mockPoolTaskDataService = new PoolTaskDataService(null, null, null, null, null, null, null, null);
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot({
@@ -30,7 +46,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 +61,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 +84,9 @@ describe('ClaimedTaskActionsReturnToPoolComponent', () => {
expect(span).toBeDefined();
});
- describe('submitTask', () => {
+ describe('actionExecution', () => {
beforeEach(() => {
- spyOn(component.processCompleted, 'emit');
-
- component.submitTask();
+ component.actionExecution().subscribe();
fixture.detectChanges();
});
@@ -73,8 +94,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..f4a79db888 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,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 { 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';
+import { take } from 'rxjs/operators';
export const WORKFLOW_TASK_OPTION_RETURN_TO_POOL = 'return_to_pool';
@@ -21,19 +29,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).pipe(take(1));
}
+
+ 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.spec.ts b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts
index 331a8586b9..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,15 +1,26 @@
import { ClaimedTaskActionsLoaderComponent } from './claimed-task-actions-loader.component';
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
-import { ChangeDetectionStrategy, ComponentFactoryResolver, NO_ERRORS_SCHEMA } from '@angular/core';
-import * as decorators from './claimed-task-actions-decorator';
+import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
+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';
+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';
-xdescribe('ClaimedTaskActionsLoaderComponent', () => {
+const searchService = getMockSearchService();
+
+const requestService = getMockRequestService();
+
+describe('ClaimedTaskActionsLoaderComponent', () => {
let comp: ClaimedTaskActionsLoaderComponent;
let fixture: ComponentFixture;
@@ -23,7 +34,12 @@ xdescribe('ClaimedTaskActionsLoaderComponent', () => {
schemas: [NO_ERRORS_SCHEMA],
providers: [
{ provide: ClaimedTaskDataService, useValue: {} },
- ComponentFactoryResolver
+ { provide: Injector, useValue: {} },
+ { provide: NotificationsService, useValue: new NotificationsServiceStub() },
+ { provide: Router, useValue: new RouterStub() },
+ { provide: SearchService, useValue: searchService },
+ { provide: RequestService, useValue: requestService },
+ { provide: PoolTaskDataService, useValue: {} }
]
}).overrideComponent(ClaimedTaskActionsLoaderComponent, {
set: {
@@ -36,16 +52,16 @@ xdescribe('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 b553eff206..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
@@ -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';
+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
@@ -58,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);
@@ -69,11 +71,15 @@ 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)));
}
}
}
+ 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 550b437b0b..2ddc5990b4 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 { Component, Injector, Input } from '@angular/core';
+import { Component, EventEmitter, Injector, Input, Output } from '@angular/core';
import { take, tap } from 'rxjs/operators';
@@ -12,10 +12,15 @@ 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';
import { getFirstSucceededRemoteData } from '../../core/shared/operators';
+export interface MyDSpaceActionsResult {
+ result: boolean;
+ reloadedObject: DSpaceObject;
+}
+
/**
* Abstract class for all different representations of mydspace actions
*/
@@ -31,7 +36,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;
@@ -71,6 +87,7 @@ export abstract class MyDSpaceActionsComponent;
+
+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(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: [
+ 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', fakeAsync(() => {
+ 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
new file mode 100644
index 0000000000..7043191915
--- /dev/null
+++ b/src/app/shared/mydspace-actions/mydspace-reloadable-actions.ts
@@ -0,0 +1,145 @@
+import { Router } from '@angular/router';
+import { Component, Injector, OnInit } from '@angular/core';
+
+import { map, switchMap, take, 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 { getFirstCompletedRemoteData } 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
+ */
+@Component({
+ selector: 'ds-mydspace-reloadable-actions',
+ template: ''
+})
+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(
+ take(1),
+ switchMap((res: ProcessTaskResponse) => {
+ if (res.hasSucceeded) {
+ return this._reloadObject().pipe(
+ tap(
+ (reloadedObject) => {
+ this.processing$.next(false);
+ this.handleReloadableActionResponse(res.hasSucceeded, 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) => {
+ if (res instanceof RemoteData) {
+ 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/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 @@
- {{'submission.workflow.tasks.generic.processing' | translate}}
- {{'submission.workflow.tasks.pool.claim' | translate}}
+ {{'submission.workflow.tasks.generic.processing' | translate}}
+ {{'submission.workflow.tasks.pool.claim' | translate}}
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 d3285dcb63..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 { ComponentFixture, fakeAsync, TestBed, waitForAsync } from '@angular/core/testing';
+import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
import { Router } from '@angular/router';
import { By } from '@angular/platform-browser';
@@ -21,6 +21,12 @@ 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,13 +35,9 @@ let mockObject: PoolTask;
let notificationsServiceStub: NotificationsServiceStub;
let router: RouterStub;
-const mockDataService = jasmine.createSpyObj('PoolTaskDataService', {
- claimTask: jasmine.createSpy('claimTask')
-});
-
const searchService = getMockSearchService();
-const requestServce = getMockRequestService();
+const requestService = getMockRequestService();
const item = Object.assign(new Item(), {
bundles: observableOf({}),
@@ -73,6 +75,8 @@ mockObject = Object.assign(new PoolTask(), { workflowitem: observableOf(rdWorkfl
describe('PoolTaskActionsComponent', () => {
beforeEach(waitForAsync(() => {
+ 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,8 +92,9 @@ 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 }
+ { provide: RequestService, useValue: requestService }
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(PoolTaskActionsComponent, {
@@ -128,63 +133,35 @@ 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', waitForAsync(() => {
- 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', waitForAsync(() => {
- spyOn(router, 'navigateByUrl');
- router.url = 'test.url/test';
- mockDataService.claimTask.and.returnValue(observableOf({ hasSucceeded: true }));
-
- component.claim();
- fixture.detectChanges();
-
- fixture.whenStable().then(() => {
- expect(router.navigateByUrl).toHaveBeenCalledWith('test.url/test');
- });
- }));
-
- it('should display an error notification on claim failure', waitForAsync(() => {
- mockDataService.claimTask.and.returnValue(observableOf({ hasSucceeded: false }));
-
- component.claim();
- fixture.detectChanges();
-
- fixture.whenStable().then(() => {
- expect(notificationsServiceStub.error).toHaveBeenCalled();
- });
- }));
-
- it('should clear the object cache by href', waitForAsync(() => {
- component.reload();
- fixture.detectChanges();
-
- fixture.whenStable().then(() => {
- expect(searchService.getEndpoint).toHaveBeenCalled();
- expect(requestServce.removeByHrefSubstring).toHaveBeenCalledWith('discover/search/objects');
- });
}));
});
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..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
@@ -1,20 +1,24 @@
-import { Component, Injector, Input } from '@angular/core';
+import { Component, Injector, Input, OnDestroy } 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, take} 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,25 @@ 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 implements OnDestroy {
/**
* 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;
+
+ subs = [];
+
/**
* Initialize instance variables
*
@@ -55,6 +60,7 @@ export class PoolTaskActionsComponent extends MyDSpaceActionsComponent>).pipe(
filter((rd: RemoteData) => ((!rd.isRequestPending) && isNotUndefined(rd.payload))),
- map((rd: RemoteData) => rd.payload));
+ map((rd: RemoteData) => rd.payload),
+ take(1));
+ }
+
+ 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).pipe(take(1));
}
/**
- * Claim the task.
+ * Retrieve the itemUuid.
*/
- claim() {
- this.processingClaim$.next(true);
- this.objectDataService.claimTask(this.object.id)
- .subscribe((res: ProcessTaskResponse) => {
- this.handleActionResponse(res.hasSucceeded);
- this.processingClaim$.next(false);
- });
+ initReloadAnchor() {
+ (this.object as any).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/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/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 9cbd8ce6ce..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
@@ -1,14 +1,12 @@
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+import { waitForAsync, ComponentFixture, TestBed, fakeAsync, 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';
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,7 @@ class TestType extends ListableObject {
}
}
-xdescribe('ListableObjectComponentLoaderComponent', () => {
+describe('ListableObjectComponentLoaderComponent', () => {
let comp: ListableObjectComponentLoaderComponent;
let fixture: ComponentFixture;
@@ -32,7 +30,7 @@ xdescribe('ListableObjectComponentLoaderComponent', () => {
imports: [TranslateModule.forRoot()],
declarations: [ListableObjectComponentLoaderComponent, ItemListElementComponent, ListableObjectDirective],
schemas: [NO_ERRORS_SCHEMA],
- providers: [ComponentFactoryResolver]
+ providers: []
}).overrideComponent(ListableObjectComponentLoaderComponent, {
set: {
changeDetection: ChangeDetectionStrategy.Default,
@@ -48,14 +46,14 @@ xdescribe('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);
});
});
@@ -117,4 +115,20 @@ xdescribe('ListableObjectComponentLoaderComponent', () => {
});
});
+ 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 14a5459a98..b02ab3cfeb 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
@@ -3,8 +3,10 @@ import {
ComponentFactoryResolver,
ElementRef,
Input,
- OnInit,
- ViewChild
+ OnDestroy, OnInit,
+ Output, ViewChild
+,
+ EventEmitter
} from '@angular/core';
import { ListableObject } from '../listable-object.model';
import { ViewMode } from '../../../../core/shared/view-mode.model';
@@ -14,17 +16,20 @@ 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';
+import { take } from 'rxjs/operators';
import { ThemeService } from '../../../theme-support/theme.service';
@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
*/
@@ -81,6 +86,11 @@ export class ListableObjectComponentLoaderComponent implements OnInit {
*/
@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
*/
@@ -91,6 +101,12 @@ export class ListableObjectComponentLoaderComponent implements OnInit {
*/
withdrawnBadge = false;
+ /**
+ * Array to track all subscriptions and unsubscribe them onDestroy
+ * @type {Array}
+ */
+ protected subs: Subscription[] = [];
+
constructor(
private componentFactoryResolver: ComponentFactoryResolver,
private themeService: ThemeService
@@ -101,9 +117,22 @@ export class ListableObjectComponentLoaderComponent implements OnInit {
* Setup the dynamic child component
*/
ngOnInit(): void {
+ this.instantiateComponent(this.object);
+ }
+
+ ngOnDestroy() {
+ this.subs
+ .filter((subscription) => hasValue(subscription))
+ .forEach((subscription) => subscription.unsubscribe());
+ }
+
+ private instantiateComponent(object) {
+
this.initBadges();
- const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent());
+ const component = this.getComponent(object.getRenderTypes(), this.viewMode, this.context);
+
+ const componentFactory = this.componentFactoryResolver.resolveComponentFactory(component);
const viewContainerRef = this.listableObjectDirective.viewContainerRef;
viewContainerRef.clear();
@@ -115,7 +144,7 @@ export class ListableObjectComponentLoaderComponent implements OnInit {
[
[this.badges.nativeElement],
]);
- (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;
@@ -123,6 +152,17 @@ 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) {
+ (componentRef.instance as any).reloadedObject.pipe(take(1)).subscribe((reloadedObject: DSpaceObject) => {
+ if (reloadedObject) {
+ componentRef.destroy();
+ this.object = reloadedObject;
+ this.instantiateComponent(reloadedObject);
+ this.contentChange.emit(reloadedObject);
+ }
+ });
+ }
}
/**
@@ -142,7 +182,9 @@ export class ListableObjectComponentLoaderComponent implements OnInit {
* 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, this.themeService.getThemeName());
+ getComponent(renderTypes: (string | GenericConstructor)[],
+ viewMode: ViewMode,
+ context: Context): GenericConstructor {
+ return getListableObjectComponent(renderTypes, viewMode, context, this.themeService.getThemeName());
}
}
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 f88a46204f..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, waitForAsync } 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';
@@ -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 68eb398f13..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
@@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+import { waitForAsync, ComponentFixture, TestBed, fakeAsync, 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/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 @@
+
+
+
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.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..5423722160
--- /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,68 @@
+import { Component } from '@angular/core';
+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 { 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.
+ */
+@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'
+})
+@listableObjectComponent(ClaimedApprovedTaskSearchResult, ViewMode.ListElement)
+export class ClaimedApprovedSearchResultListElementComponent extends SearchResultListElementComponent {
+
+ /**
+ * A boolean representing if to show submitter information
+ */
+ public showSubmitter = true;
+
+ /**
+ * Represent item's status
+ */
+ public status = MyDspaceItemStatusType.APPROVED;
+
+ /**
+ * The workflowitem object that belonging to the result object
+ */
+ public workflowitemRD$: Observable>;
+
+ public 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,
+ 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-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 @@
+
+
+
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.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..7db12c1725
--- /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,68 @@
+import { Component } from '@angular/core';
+
+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.
+ */
+@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'
+})
+@listableObjectComponent(ClaimedDeclinedTaskSearchResult, ViewMode.ListElement)
+export class ClaimedDeclinedSearchResultListElementComponent extends SearchResultListElementComponent {
+
+ /**
+ * A boolean representing if to show submitter information
+ */
+ public showSubmitter = true;
+
+ /**
+ * Represent item's status
+ */
+ public status = MyDspaceItemStatusType.DECLINED;
+
+ /**
+ * The workflowitem object that belonging to the result object
+ */
+ public workflowitemRD$: Observable>;
+
+ public 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,
+ 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-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 151d205273..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 { ComponentFixture, TestBed, 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';
@@ -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 788a373f83..cef1056401 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,30 +1,23 @@
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 { 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';
-/**
- * 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 }]
+ templateUrl: './claimed-search-result-list-element.component.html'
})
-
@listableObjectComponent(ClaimedTaskSearchResult, ViewMode.ListElement)
export class ClaimedSearchResultListElementComponent extends SearchResultListElementComponent {
@@ -43,7 +36,7 @@ export class ClaimedSearchResultListElementComponent extends SearchResultListEle
*/
public workflowitemRD$: Observable>;
- constructor(
+ public constructor(
protected linkService: LinkService,
protected truncatableService: TruncatableService
) {
@@ -60,4 +53,5 @@ export class ClaimedSearchResultListElementComponent extends SearchResultListEle
), 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 07174ac74f..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
@@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+import { waitForAsync, ComponentFixture, TestBed, fakeAsync, 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 74c64ca254..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,5 +1,5 @@
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+import { waitForAsync, 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 bf101e651c..b130d5001c 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/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/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 82bd21cec6..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
@@ -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/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
*/
diff --git a/src/app/shared/uploader/uploader.component.spec.ts b/src/app/shared/uploader/uploader.component.spec.ts
index d33c27b897..6ff54578b5 100644
--- a/src/app/shared/uploader/uploader.component.spec.ts
+++ b/src/app/shared/uploader/uploader.component.spec.ts
@@ -10,6 +10,10 @@ import { UploaderComponent } from './uploader.component';
import { FileUploadModule } from 'ng2-file-upload';
import { TranslateModule } from '@ngx-translate/core';
import { createTestComponent } from '../testing/utils.test';
+import { HttpXsrfTokenExtractor } from '@angular/common/http';
+import { CookieService } from '../../core/services/cookie.service';
+import { CookieServiceMock } from '../mocks/cookie.service.mock';
+import { HttpXsrfTokenExtractorMock } from '../mocks/http-xsrf-token-extractor.mock';
describe('Chips component', () => {
@@ -33,7 +37,9 @@ describe('Chips component', () => {
ChangeDetectorRef,
ScrollToService,
UploaderComponent,
- UploaderService
+ UploaderService,
+ { provide: HttpXsrfTokenExtractor, useValue: new HttpXsrfTokenExtractorMock('mock-token') },
+ { provide: CookieService, useValue: new CookieServiceMock() },
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
});
diff --git a/src/app/shared/uploader/uploader.component.ts b/src/app/shared/uploader/uploader.component.ts
index ac5c5c65a2..4ee17ac87b 100644
--- a/src/app/shared/uploader/uploader.component.ts
+++ b/src/app/shared/uploader/uploader.component.ts
@@ -18,6 +18,9 @@ import { UploaderOptions } from './uploader-options.model';
import { hasValue, isNotEmpty, isUndefined } from '../empty.util';
import { UploaderService } from './uploader.service';
import { UploaderProperties } from './uploader-properties.model';
+import { HttpXsrfTokenExtractor } from '@angular/common/http';
+import { XSRF_REQUEST_HEADER, XSRF_RESPONSE_HEADER, XSRF_COOKIE } from '../../core/xsrf/xsrf.interceptor';
+import { CookieService } from '../../core/services/cookie.service';
@Component({
selector: 'ds-uploader',
@@ -91,7 +94,9 @@ export class UploaderComponent {
}
}
- constructor(private cdr: ChangeDetectorRef, private scrollToService: ScrollToService, private uploaderService: UploaderService) {
+ constructor(private cdr: ChangeDetectorRef, private scrollToService: ScrollToService,
+ private uploaderService: UploaderService, private tokenExtractor: HttpXsrfTokenExtractor,
+ private cookieService: CookieService) {
}
/**
@@ -108,7 +113,7 @@ export class UploaderComponent {
removeAfterUpload: true,
autoUpload: this.uploadFilesOptions.autoUpload,
method: this.uploadFilesOptions.method,
- queueLimit: this.uploadFilesOptions.maxFileNumber
+ queueLimit: this.uploadFilesOptions.maxFileNumber,
});
if (isUndefined(this.enableDragOverDocument)) {
@@ -123,10 +128,6 @@ export class UploaderComponent {
}
ngAfterViewInit() {
- // Maybe to remove: needed to avoid CORS issue with our temp upload server
- this.uploader.onAfterAddingFile = ((item) => {
- item.withCredentials = false;
- });
this.uploader.onAfterAddingAll = ((items) => {
this.onFileSelected.emit(items);
});
@@ -137,6 +138,8 @@ export class UploaderComponent {
if (item.url !== this.uploader.options.url) {
item.url = this.uploader.options.url;
}
+ // Ensure the current XSRF token is included in every upload request (token may change between items uploaded)
+ this.uploader.options.headers = [{ name: XSRF_REQUEST_HEADER, value: this.tokenExtractor.getToken() }];
this.onBeforeUpload();
this.isOverDocumentDropZone = observableOf(false);
@@ -152,12 +155,30 @@ export class UploaderComponent {
};
}
this.uploader.onCompleteItem = (item: any, response: any, status: any, headers: any) => {
+ // Check for a changed XSRF token in response & save new token if found (to both cookie & header for next request)
+ // NOTE: this is only necessary because ng2-file-upload doesn't use an Http service and therefore never
+ // triggers our xsrf.interceptor.ts. See this bug: https://github.com/valor-software/ng2-file-upload/issues/950
+ const token = headers[XSRF_RESPONSE_HEADER.toLowerCase()];
+ if (isNotEmpty(token)) {
+ this.saveXsrfToken(token);
+ this.uploader.options.headers = [{ name: XSRF_REQUEST_HEADER, value: this.tokenExtractor.getToken() }];
+ }
+
if (isNotEmpty(response)) {
const responsePath = JSON.parse(response);
this.onCompleteItem.emit(responsePath);
}
};
this.uploader.onErrorItem = (item: any, response: any, status: any, headers: any) => {
+ // Check for a changed XSRF token in response & save new token if found (to both cookie & header for next request)
+ // NOTE: this is only necessary because ng2-file-upload doesn't use an Http service and therefore never
+ // triggers our xsrf.interceptor.ts. See this bug: https://github.com/valor-software/ng2-file-upload/issues/950
+ const token = headers[XSRF_RESPONSE_HEADER.toLowerCase()];
+ if (isNotEmpty(token)) {
+ this.saveXsrfToken(token);
+ this.uploader.options.headers = [{ name: XSRF_REQUEST_HEADER, value: this.tokenExtractor.getToken() }];
+ }
+
this.onUploadError.emit({ item: item, response: response, status: status, headers: headers });
this.uploader.cancelAll();
};
@@ -201,4 +222,18 @@ export class UploaderComponent {
}
}
+ /**
+ * Save XSRF token found in response. This is a temporary copy of the method in xsrf.interceptor.ts
+ * It can be removed once ng2-file-upload supports interceptors (see https://github.com/valor-software/ng2-file-upload/issues/950),
+ * or we switch to a new upload library (see https://github.com/DSpace/dspace-angular/issues/820)
+ * @param token token found
+ */
+ private saveXsrfToken(token: string) {
+ // Save token value as a *new* value of our client-side XSRF-TOKEN cookie.
+ // This is the cookie that is parsed by Angular's tokenExtractor(),
+ // which we will send back in the X-XSRF-TOKEN header per Angular best practices.
+ this.cookieService.remove(XSRF_COOKIE);
+ this.cookieService.set(XSRF_COOKIE, token);
+ }
+
}