Added services and models

This commit is contained in:
Giuseppe Digilio
2019-03-08 16:38:05 +01:00
parent ebb77b2a78
commit f07aafaf15
28 changed files with 837 additions and 11 deletions

View File

@@ -12,6 +12,8 @@ import { NormalizedWorkspaceItem } from '../../submission/models/normalized-work
import { NormalizedEPerson } from '../../eperson/models/normalized-eperson.model';
import { NormalizedGroup } from '../../eperson/models/normalized-group.model';
import { NormalizedWorkflowItem } from '../../submission/models/normalized-workflowitem.model';
import { NormalizedClaimedTask } from '../../tasks/models/normalized-claimed-task-object.model';
import { NormalizedPoolTask } from '../../tasks/models/normalized-pool-task-object.model';
import { NormalizedBitstreamFormat } from './normalized-bitstream-format.model';
import { SubmissionDefinitionsModel } from '../../config/models/config-submission-definitions.model';
import { SubmissionFormsModel } from '../../config/models/config-submission-forms.model';
@@ -63,6 +65,12 @@ export class NormalizedObjectFactory {
case ResourceType.Workflowitem: {
return NormalizedWorkflowItem
}
case ResourceType.ClaimedTask: {
return NormalizedClaimedTask
}
case ResourceType.PoolTask: {
return NormalizedPoolTask
}
case ResourceType.BitstreamFormat: {
return NormalizedBitstreamFormat
}

View File

@@ -253,4 +253,28 @@ export class EpersonSuccessResponse extends RestResponse {
}
}
export class MessageResponse extends RestResponse {
public toCache = false;
constructor(
public statusCode: number,
public statusText: string,
public pageInfo?: PageInfo
) {
super(true, statusCode, statusText);
}
}
export class TaskResponse extends RestResponse {
public toCache = false;
constructor(
public statusCode: number,
public statusText: string,
public pageInfo?: PageInfo
) {
super(true, statusCode, statusText);
}
}
/* tslint:enable:max-classes-per-file */

View File

@@ -77,6 +77,14 @@ import { MenuService } from '../shared/menu/menu.service';
import { SubmissionJsonPatchOperationsService } from './submission/submission-json-patch-operations.service';
import { NormalizedObjectBuildService } from './cache/builders/normalized-object-build.service';
import { DSOChangeAnalyzer } from './data/dso-change-analyzer.service';
import { RoleService } from './roles/role.service';
import { MyDSpaceGuard } from '../+my-dspace-page/my-dspace.guard';
import { MyDSpaceResponseParsingService } from './data/mydspace-response-parsing.service';
import { MessageService } from './message/message.service';
import { ClaimedTaskDataService } from './tasks/claimed-task-data.service';
import { PoolTaskDataService } from './tasks/pool-task-data.service';
import { TaskResponseParsingService } from './tasks/task-response-parsing.service';
import { MessageResponseParsingService } from './message/message-response-parsing.service';
const IMPORTS = [
CommonModule,
@@ -128,6 +136,7 @@ const PROVIDERS = [
RegistryBitstreamformatsResponseParsingService,
DebugResponseParsingService,
SearchResponseParsingService,
MyDSpaceResponseParsingService,
ServerResponseService,
BrowseResponseParsingService,
BrowseEntriesResponseParsingService,
@@ -156,6 +165,13 @@ const PROVIDERS = [
DSOChangeAnalyzer,
CSSVariableService,
MenuService,
MyDSpaceGuard,
RoleService,
MessageResponseParsingService,
MessageService,
TaskResponseParsingService,
ClaimedTaskDataService,
PoolTaskDataService,
// register AuthInterceptor as HttpInterceptor
{
provide: HTTP_INTERCEPTORS,
@@ -166,15 +182,20 @@ const PROVIDERS = [
{ provide: NativeWindowService, useFactory: NativeWindowFactory }
];
const DIRECTIVES = [
];
@NgModule({
imports: [
...IMPORTS
],
declarations: [
...DECLARATIONS
...DECLARATIONS,
...DIRECTIVES
],
exports: [
...EXPORTS
...EXPORTS,
...DIRECTIVES
],
providers: [
...PROVIDERS

View File

@@ -160,4 +160,10 @@ export abstract class BaseResponseParsingService {
}
return obj;
}
protected isSuccessStatus(statusCode: number) {
return (statusCode === 201
|| statusCode === 200
|| statusCode === 204)
}
}

View File

@@ -1,5 +1,8 @@
import { Injectable } from '@angular/core';
import { filter, map, take } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { NormalizedCollection } from '../cache/models/normalized-collection.model';
import { ObjectCacheService } from '../cache/object-cache.service';
@@ -13,6 +16,10 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
import { HttpClient } from '@angular/common/http';
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
import { Observable } from 'rxjs/internal/Observable';
import { FindAllOptions } from './request.models';
import { RemoteData } from './remote-data';
import { PaginatedList } from './paginated-list';
@Injectable()
export class CollectionDataService extends ComColDataService<NormalizedCollection, Collection> {
@@ -34,4 +41,21 @@ export class CollectionDataService extends ComColDataService<NormalizedCollectio
super();
}
/**
* Find whether there is a collection whom user has authorization to submit to
*
* @return boolean
* true if the user has at least one collection to submit to
*/
hasAuthorizedCollection(): Observable<boolean> {
const searchHref = 'findAuthorized';
const options = new FindAllOptions();
options.elementsPerPage = 1;
return this.searchBy(searchHref, options).pipe(
filter((collections: RemoteData<PaginatedList<Collection>>) => !collections.isResponsePending),
take(1),
map((collections: RemoteData<PaginatedList<Collection>>) => collections.payload.totalElements > 0)
);
}
}

View File

@@ -0,0 +1,59 @@
import { Injectable } from '@angular/core';
import { RestResponse, SearchSuccessResponse } from '../cache/response.models';
import { DSOResponseParsingService } from './dso-response-parsing.service';
import { ResponseParsingService } from './parsing.service';
import { RestRequest } from './request.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
import { hasValue } from '../../shared/empty.util';
import { SearchQueryResponse } from '../../+search-page/search-service/search-query-response.model';
import { MetadataMap, MetadataValue } from '../shared/metadata.interfaces';
@Injectable()
export class MyDSpaceResponseParsingService implements ResponseParsingService {
constructor(private dsoParser: DSOResponseParsingService) {
}
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
// fallback for unexpected empty response
const emptyPayload = {
_embedded : {
objects: []
}
};
const payload = data.payload._embedded.searchResult || emptyPayload;
const hitHighlights: MetadataMap[] = payload._embedded.objects
.map((object) => object.hitHighlights)
.map((hhObject) => {
const mdMap: MetadataMap = {};
if (hhObject) {
for (const key of Object.keys(hhObject)) {
const value: MetadataValue = { value: hhObject[key].join('...'), language: null };
mdMap[key] = [ value ];
}
}
return mdMap;
});
const dsoSelfLinks = payload._embedded.objects
.filter((object) => hasValue(object._embedded))
.map((object) => object._embedded.rObject)
.map((dso) => this.dsoParser.parse(request, {
payload: dso,
statusCode: data.statusCode,
statusText: data.statusText
}))
.map((obj) => obj.resourceSelfLinks)
.reduce((combined, thisElement) => [...combined, ...thisElement], []);
const objects = payload._embedded.objects
.filter((object) => hasValue(object._embedded))
.map((object, index) => Object.assign({}, object, {
rObject: dsoSelfLinks[index],
hitHighlights: hitHighlights[index]
}));
payload.objects = objects;
const deserialized = new DSpaceRESTv2Serializer(SearchQueryResponse).deserialize(payload);
return new SearchSuccessResponse(deserialized, data.statusCode, data.statusText, this.dsoParser.processPageInfo(payload));
}
}

View File

@@ -11,7 +11,7 @@ export class PaginatedList<T> {
if (hasValue(this.pageInfo) && hasValue(this.pageInfo.elementsPerPage)) {
return this.pageInfo.elementsPerPage;
}
return this.page.length;
return this.getPageLength();
}
set elementsPerPage(value: number) {
@@ -22,7 +22,7 @@ export class PaginatedList<T> {
if (hasValue(this.pageInfo) && hasValue(this.pageInfo.totalElements)) {
return this.pageInfo.totalElements;
}
return this.page.length;
return this.getPageLength();
}
set totalElements(value: number) {
@@ -89,4 +89,8 @@ export class PaginatedList<T> {
set self(self: string) {
this.pageInfo.self = self;
}
protected getPageLength() {
return (Array.isArray(this.page)) ? this.page.length : 0;
}
}

View File

@@ -17,6 +17,8 @@ import { BrowseItemsResponseParsingService } from './browse-items-response-parsi
import { RegistryMetadataschemasResponseParsingService } from './registry-metadataschemas-response-parsing.service';
import { MetadataschemaParsingService } from './metadataschema-parsing.service';
import { MetadatafieldParsingService } from './metadatafield-parsing.service';
import { TaskResponseParsingService } from '../tasks/task-response-parsing.service';
import { MessageResponseParsingService } from '../message/message-response-parsing.service';
/* tslint:disable:max-classes-per-file */
@@ -370,6 +372,46 @@ export class DeleteByIDRequest extends DeleteRequest {
}
}
export class MessagePostRequest extends PostRequest {
constructor(uuid: string, href: string, public body?: any, public options?: HttpOptions) {
super(uuid, href, body, options);
}
getResponseParser(): GenericConstructor<ResponseParsingService> {
return MessageResponseParsingService;
}
}
export class MessageGetRequest extends GetRequest {
constructor(uuid: string, href: string, public options?: HttpOptions) {
super(uuid, href, null, options);
}
getResponseParser(): GenericConstructor<ResponseParsingService> {
return MessageResponseParsingService;
}
}
export class TaskPostRequest extends PostRequest {
constructor(uuid: string, href: string, public body?: any, public options?: HttpOptions) {
super(uuid, href, body, options);
}
getResponseParser(): GenericConstructor<ResponseParsingService> {
return TaskResponseParsingService;
}
}
export class TaskDeleteRequest extends DeleteRequest {
constructor(uuid: string, href: string, public body?: any, public options?: HttpOptions) {
super(uuid, href, body, options);
}
getResponseParser(): GenericConstructor<ResponseParsingService> {
return TaskResponseParsingService;
}
}
export class RequestError extends Error {
statusCode: number;
statusText: string;

View File

@@ -4,7 +4,7 @@ import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
import { merge as observableMerge, Observable, of as observableOf, race as observableRace } from 'rxjs';
import { filter, map, mergeMap, switchMap, take } from 'rxjs/operators';
import { hasNoValue, hasValue, isNotEmpty } from '../../shared/empty.util';
import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
import { CacheableObject } from '../cache/object-cache.reducer';
import { ObjectCacheService } from '../cache/object-cache.service';
import { DSOSuccessResponse, RestResponse } from '../cache/response.models';
@@ -123,9 +123,9 @@ export class RequestService {
// TODO to review "forceBypassCache" param when https://github.com/DSpace/dspace-angular/issues/217 will be fixed
configure<T extends CacheableObject>(request: RestRequest, forceBypassCache: boolean = false): void {
const isGetRequest = request.method === RestRequestMethod.GET;
if (!isGetRequest || !this.isCachedOrPending(request) || forceBypassCache) {
if (!isGetRequest || !this.isCachedOrPending(request) || (forceBypassCache && !this.isPending(request))) {
this.dispatchRequest(request);
if (isGetRequest && !forceBypassCache) {
if (isGetRequest) {
this.trackRequestsOnTheirWayToTheStore(request);
}
} else {
@@ -139,6 +139,29 @@ export class RequestService {
}
}
/**
* Convert request Payload to a URL-encoded string
*
* e.g. prepareBody({param: value, param1: value1})
* returns: param=value&param1=value1
*
* @param body
* The request Payload to convert
* @return string
* URL-encoded string
*/
public prepareBody(body: any) {
let queryParams = '';
if (isNotEmpty(body) && typeof body === 'object') {
Object.keys(body)
.forEach((param) => {
const paramValue = `${param}=${body[param]}`;
queryParams = isEmpty(queryParams) ? queryParams.concat(paramValue) : queryParams.concat('&', paramValue);
})
}
return encodeURI(queryParams);
}
/**
* Remove all request cache providing (part of) the href
* This also includes href-to-uuid index cache

View File

@@ -15,7 +15,13 @@ export class SearchResponseParsingService implements ResponseParsingService {
}
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
const payload = data.payload._embedded.searchResult;
// fallback for unexpected empty response
const emptyPayload = {
_embedded : {
objects: []
}
};
const payload = data.payload._embedded.searchResult || emptyPayload;
const hitHighlights: MetadataMap[] = payload._embedded.objects
.map((object) => object.hitHighlights)
.map((hhObject) => {
@@ -31,7 +37,7 @@ export class SearchResponseParsingService implements ResponseParsingService {
const dsoSelfLinks = payload._embedded.objects
.filter((object) => hasValue(object._embedded))
.map((object) => object._embedded.dspaceObject)
.map((object) => object._embedded.rObject)
// we don't need embedded collections, bitstreamformats, etc for search results.
// And parsing them all takes up a lot of time. Throw them away to improve performance
// until objs until partial results are supported by the rest api
@@ -47,7 +53,7 @@ export class SearchResponseParsingService implements ResponseParsingService {
const objects = payload._embedded.objects
.filter((object) => hasValue(object._embedded))
.map((object, index) => Object.assign({}, object, {
dspaceObject: dsoSelfLinks[index],
rObject: dsoSelfLinks[index],
hitHighlights: hitHighlights[index],
// we don't need embedded collections, bitstreamformats, etc for search results.
// And parsing them all takes up a lot of time. Throw them away to improve performance

View File

@@ -0,0 +1,17 @@
import { RemoteDataError } from '../data/remote-data-error';
/**
* A class to represent the data retrieved by after processing a message
*/
export class MessageDataResponse {
constructor(
private isSuccessful: boolean,
public error?: RemoteDataError,
public payload?: any
) {
}
get hasSucceeded(): boolean {
return this.isSuccessful;
}
}

View File

@@ -0,0 +1,37 @@
import { Inject, Injectable } from '@angular/core';
import { ResponseParsingService } from '../data/parsing.service';
import { RestRequest } from '../data/request.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { BaseResponseParsingService } from '../data/base-response-parsing.service';
import { GLOBAL_CONFIG } from '../../../config';
import { GlobalConfig } from '../../../config/global-config.interface';
import { ObjectCacheService } from '../cache/object-cache.service';
import { NormalizedSubmissionObjectFactory } from '../submission/normalized-submission-object-factory';
import { ErrorResponse, MessageResponse, RestResponse } from '../cache/response.models';
@Injectable()
export class MessageResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
protected objectFactory = NormalizedSubmissionObjectFactory;
protected toCache = false;
constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
protected objectCache: ObjectCacheService,) {
super();
}
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
if (this.isSuccessStatus(data.statusCode)) {
return new MessageResponse( data.statusCode, data.statusText);
} else {
return new ErrorResponse(
Object.assign(
new Error('Unexpected response from server'),
{ statusCode: data.statusCode, statusText: data.statusText }
)
);
}
}
}

View File

@@ -0,0 +1,107 @@
import { Injectable } from '@angular/core';
import { HttpHeaders } from '@angular/common/http';
import { merge as observableMerge, Observable, of as observableOf } from 'rxjs';
import { catchError, distinctUntilChanged, filter, flatMap, map, mergeMap, tap } from 'rxjs/operators';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { RequestService } from '../data/request.service';
import { isNotEmpty } from '../../shared/empty.util';
import { MessageGetRequest, MessagePostRequest, PostRequest, RestRequest } from '../data/request.models';
import { DSpaceRESTv2Service, HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
import { MessageDataResponse } from './message-data-response';
import { RemoteDataError } from '../data/remote-data-error';
import { getResponseFromEntry } from '../shared/operators';
import { ErrorResponse, MessageResponse, RestResponse } from '../cache/response.models';
import { RestRequestMethod } from '../data/rest-request-method';
@Injectable()
export class MessageService {
protected linkPath = 'messages';
constructor(protected http: DSpaceRESTv2Service,
protected requestService: RequestService,
protected halService: HALEndpointService) {
}
protected fetchRequest(requestId: string): Observable<MessageDataResponse> {
const responses = this.requestService.getByUUID(requestId).pipe(
getResponseFromEntry()
);
const errorResponses = responses.pipe(
filter((response: RestResponse) => !response.isSuccessful),
mergeMap((response: ErrorResponse) => observableOf(
new MessageDataResponse(
response.isSuccessful,
new RemoteDataError(response.statusCode, response.statusText, response.errorMessage)
))
));
const successResponses = responses.pipe(
filter((response: RestResponse) => response.isSuccessful),
map((response: MessageResponse) => new MessageDataResponse(response.isSuccessful)),
distinctUntilChanged()
);
return observableMerge(errorResponses, successResponses);
}
protected getEndpointByMethod(endpoint: string, method: string): string {
return isNotEmpty(method) ? `${endpoint}/${method}` : `${endpoint}`;
}
public postToEndpoint(method: string, body: any, options?: HttpOptions): Observable<MessageDataResponse> {
const requestId = this.requestService.generateRequestId();
return this.halService.getEndpoint(this.linkPath).pipe(
filter((href: string) => isNotEmpty(href)),
map((endpointURL: string) => this.getEndpointByMethod(endpointURL, method)),
distinctUntilChanged(),
map((endpointURL: string) => new MessagePostRequest(requestId, endpointURL, body, options)),
tap((request: PostRequest) => this.requestService.configure(request)),
flatMap((request: PostRequest) => this.fetchRequest(requestId)),
distinctUntilChanged());
}
public getRequest(method: string, options?: HttpOptions): Observable<any> {
const requestId = this.requestService.generateRequestId();
return this.halService.getEndpoint(this.linkPath).pipe(
map((endpointURL: string) => this.getEndpointByMethod(endpointURL, method)),
filter((href: string) => isNotEmpty(href)),
distinctUntilChanged(),
map((endpointURL: string) => new MessageGetRequest(requestId, endpointURL)),
tap((request: RestRequest) => this.requestService.configure(request, true)),
flatMap((request: RestRequest) => this.fetchRequest(requestId)),
distinctUntilChanged());
}
public createMessage(body: any, options?: HttpOptions): Observable<MessageDataResponse> {
return this.postToEndpoint('', this.requestService.prepareBody(body), this.makeHttpOptions());
}
public markAsRead(body: any, options?: HttpOptions): Observable<MessageDataResponse> {
return this.postToEndpoint('read', this.requestService.prepareBody(body), this.makeHttpOptions());
}
public markAsUnread(body: any, options?: HttpOptions): Observable<MessageDataResponse> {
return this.postToEndpoint('unread', this.requestService.prepareBody(body), this.makeHttpOptions());
}
protected makeHttpOptions() {
const options: HttpOptions = Object.create({});
let headers = new HttpHeaders();
headers = headers.append('Content-Type', 'application/x-www-form-urlencoded');
options.headers = headers;
return options;
}
public getMessageContent(url: string): Observable<any> {
if (isNotEmpty(url)) {
const options: HttpOptions = Object.create({});
options.observe = 'response';
options.responseType = 'text';
return this.http.request(RestRequestMethod.GET, url, null, options).pipe(
map((res) => ({ payload: res.payload })),
catchError((err) => observableOf({ payload: '' })));
} else {
return observableOf({ payload: '' });
}
}
}

View File

@@ -0,0 +1,5 @@
export enum RoleType {
Submitter = 'submitter',
Controller = 'controller',
Admin = 'admin'
}

View File

@@ -0,0 +1,52 @@
import { Injectable } from '@angular/core';
import { AppState } from '../../app.reducer';
import { Observable, of as observableOf } from 'rxjs';
import { distinctUntilChanged } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { RoleType } from './role-types';
import { CollectionDataService } from '../data/collection-data.service';
@Injectable()
export class RoleService {
constructor(
private collectionService: CollectionDataService,
private store: Store<AppState>) {
}
isSubmitter(): Observable<boolean> {
return this.collectionService.hasAuthorizedCollection().pipe(
distinctUntilChanged()
);
}
isController(): Observable<boolean> {
// TODO find a way to check if user is a controller
return observableOf(true);
}
isAdmin(): Observable<boolean> {
// TODO find a way to check if user is an admin
return observableOf(false);
}
checkRole(role: RoleType): Observable<boolean> {
let check: Observable<boolean>;
switch (role) {
case RoleType.Submitter:
check = this.isSubmitter();
break;
case RoleType.Controller:
check = this.isController();
break;
case RoleType.Admin:
check = this.isAdmin();
break;
}
return check;
}
}

View File

@@ -20,4 +20,6 @@ export enum ResourceType {
SubmissionForms = 'submissionforms',
SubmissionSections = 'submissionsections',
SubmissionSection = 'submissionsection',
ClaimedTask = 'claimedtask',
PoolTask = 'pooltask'
}

View File

@@ -4,5 +4,6 @@
export enum ViewMode {
List = 'list',
Grid = 'grid'
Grid = 'grid',
Detail = 'detail'
}

View File

@@ -0,0 +1,56 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { CoreState } from '../core.reducers';
import { RequestService } from '../data/request.service';
import { NormalizedClaimedTask } from './models/normalized-claimed-task-object.model';
import { ClaimedTask } from './models/claimed-task-object.model';
import { TasksService } from './tasks.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
import { ObjectCacheService } from '../cache/object-cache.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
@Injectable()
export class ClaimedTaskDataService extends TasksService<NormalizedClaimedTask, ClaimedTask> {
protected linkPath = 'claimedtasks';
protected forceBypassCache = true;
constructor(
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected dataBuildService: NormalizedObjectBuildService,
protected store: Store<CoreState>,
protected objectCache: ObjectCacheService,
protected halService: HALEndpointService,
protected notificationsService: NotificationsService,
protected http: HttpClient,
protected comparator: DSOChangeAnalyzer) {
super();
}
public approveTask(scopeId: string): Observable<any> {
const body = {
submit_approve: 'true'
};
return this.postToEndpoint(this.linkPath, this.requestService.prepareBody(body), scopeId, this.makeHttpOptions());
}
public rejectTask(reason: string, scopeId: string): Observable<any> {
const body = {
submit_reject: 'true',
reason
};
return this.postToEndpoint(this.linkPath, this.requestService.prepareBody(body), scopeId, this.makeHttpOptions());
}
public returnToPoolTask(scopeId: string): Observable<any> {
return this.deleteById(this.linkPath, scopeId, this.makeHttpOptions());
}
}

View File

@@ -0,0 +1,5 @@
import { TaskObject } from './task-object.model';
export class ClaimedTask extends TaskObject {
}

View File

@@ -0,0 +1,39 @@
import { NormalizedTaskObject } from './normalized-task-object.model';
import { mapsTo, relationship } from '../../cache/builders/build-decorators';
import { autoserialize, inheritSerialization } from 'cerialize';
import { ClaimedTask } from './claimed-task-object.model';
import { ResourceType } from '../../shared/resource-type';
/**
* A model class for a NormalizedClaimedTaskObject.
*/
@mapsTo(ClaimedTask)
@inheritSerialization(NormalizedTaskObject)
export class NormalizedClaimedTask extends NormalizedTaskObject {
/**
* The task identifier
*/
@autoserialize
id: string;
/**
* The workflow step
*/
@autoserialize
step: string;
/**
* The task action type
*/
@autoserialize
action: string;
/**
* The workflowitem object whom this task is related
*/
@autoserialize
@relationship(ResourceType.Workflowitem, false)
workflowitem: string;
}

View File

@@ -0,0 +1,38 @@
import { NormalizedTaskObject } from './normalized-task-object.model';
import { PoolTask } from './pool-task-object.model';
import { autoserialize, inheritSerialization } from 'cerialize';
import { mapsTo, relationship } from '../../cache/builders/build-decorators';
import { ResourceType } from '../../shared/resource-type';
/**
* A model class for a NormalizedPoolTaskObject.
*/
@mapsTo(PoolTask)
@inheritSerialization(NormalizedTaskObject)
export class NormalizedPoolTask extends NormalizedTaskObject {
/**
* The task identifier
*/
@autoserialize
id: string;
/**
* The workflow step
*/
@autoserialize
step: string;
/**
* The task action type
*/
@autoserialize
action: string;
/**
* The workflowitem object whom this task is related
*/
@autoserialize
@relationship(ResourceType.Workflowitem, false)
workflowitem: string;
}

View File

@@ -0,0 +1,38 @@
import { autoserialize, inheritSerialization } from 'cerialize';
import { mapsTo, relationship } from '../../cache/builders/build-decorators';
import { ResourceType } from '../../shared/resource-type';
import { NormalizedDSpaceObject } from '../../cache/models/normalized-dspace-object.model';
import { TaskObject } from './task-object.model';
/**
* An abstract model class for a DSpaceObject.
*/
@mapsTo(TaskObject)
@inheritSerialization(NormalizedDSpaceObject)
export abstract class NormalizedTaskObject extends NormalizedDSpaceObject {
/**
* The task identifier
*/
@autoserialize
id: string;
/**
* The workflow step
*/
@autoserialize
step: string;
/**
* The task action type
*/
@autoserialize
action: string;
/**
* The workflowitem object whom this task is related
*/
@autoserialize
@relationship(ResourceType.Workflowitem, false)
workflowitem: string;
}

View File

@@ -0,0 +1,5 @@
import { TaskObject } from './task-object.model';
export class PoolTask extends TaskObject {
}

View File

@@ -0,0 +1,17 @@
import { RemoteDataError } from '../../data/remote-data-error';
/**
* A class to represent the data retrieved by after processing a task
*/
export class ProcessTaskResponse {
constructor(
private isSuccessful: boolean,
public error?: RemoteDataError,
public payload?: any
) {
}
get hasSucceeded(): boolean {
return this.isSuccessful;
}
}

View File

@@ -0,0 +1,30 @@
import { Observable } from 'rxjs';
import { CacheableObject } from '../../cache/object-cache.reducer';
import { DSpaceObject } from '../../shared/dspace-object.model';
import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model';
import { RemoteData } from '../../data/remote-data';
import { Workflowitem } from '../../submission/models/workflowitem.model';
export class TaskObject extends DSpaceObject implements CacheableObject, ListableObject {
/**
* The task identifier
*/
id: string;
/**
* The workflow step
*/
step: string;
/**
* The task action type
*/
action: string;
/**
* The workflowitem object whom this task is related
*/
workflowitem: Observable<RemoteData<Workflowitem>> | Workflowitem;
}

View File

@@ -0,0 +1,40 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Store } from '@ngrx/store';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { CoreState } from '../core.reducers';
import { RequestService } from '../data/request.service';
import { NormalizedPoolTask } from './models/normalized-pool-task-object.model';
import { PoolTask } from './models/pool-task-object.model';
import { TasksService } from './tasks.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
import { ObjectCacheService } from '../cache/object-cache.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
@Injectable()
export class PoolTaskDataService extends TasksService<NormalizedPoolTask, PoolTask> {
protected linkPath = 'pooltasks';
protected forceBypassCache = true;
constructor(
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected dataBuildService: NormalizedObjectBuildService,
protected store: Store<CoreState>,
protected objectCache: ObjectCacheService,
protected halService: HALEndpointService,
protected notificationsService: NotificationsService,
protected http: HttpClient,
protected comparator: DSOChangeAnalyzer) {
super();
}
public claimTask(scopeId: string): Observable<any> {
return this.postToEndpoint(this.linkPath, {}, scopeId, this.makeHttpOptions());
}
}

View File

@@ -0,0 +1,38 @@
import { Inject, Injectable } from '@angular/core';
import { ResponseParsingService } from '../data/parsing.service';
import { RestRequest } from '../data/request.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { BaseResponseParsingService } from '../data/base-response-parsing.service';
import { GLOBAL_CONFIG } from '../../../config';
import { GlobalConfig } from '../../../config/global-config.interface';
import { ObjectCacheService } from '../cache/object-cache.service';
import { NormalizedObjectFactory } from '../cache/models/normalized-object-factory';
import { ErrorResponse, RestResponse, TaskResponse } from '../cache/response.models';
@Injectable()
export class TaskResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
protected objectFactory = NormalizedObjectFactory;
protected toCache = false;
constructor(@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
protected objectCache: ObjectCacheService,) {
super();
}
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
if (this.isSuccessStatus(data.statusCode)) {
return new TaskResponse( data.statusCode, data.statusText);
} else {
return new ErrorResponse(
Object.assign(
new Error('Unexpected response from server'),
{ statusCode: data.statusCode, statusText: data.statusText }
)
);
}
}
}

View File

@@ -0,0 +1,82 @@
import { HttpHeaders } from '@angular/common/http';
import { merge as observableMerge, Observable, of as observableOf } from 'rxjs';
import { distinctUntilChanged, filter, flatMap, map, mergeMap, tap } from 'rxjs/operators';
import { DataService } from '../data/data.service';
import { DeleteRequest, FindAllOptions, PostRequest, TaskDeleteRequest, TaskPostRequest } from '../data/request.models';
import { isNotEmpty } from '../../shared/empty.util';
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
import { ProcessTaskResponse } from './models/process-task-response';
import { RemoteDataError } from '../data/remote-data-error';
import { NormalizedObject } from '../cache/models/normalized-object.model';
import { getResponseFromEntry } from '../shared/operators';
import { ErrorResponse, MessageResponse, RestResponse } from '../cache/response.models';
import { CacheableObject } from '../cache/object-cache.reducer';
export abstract class TasksService<TNormalized extends NormalizedObject, TDomain extends CacheableObject> extends DataService<TNormalized, TDomain> {
public getBrowseEndpoint(options: FindAllOptions): Observable<string> {
return this.halService.getEndpoint(this.linkPath);
}
protected fetchRequest(requestId: string): Observable<ProcessTaskResponse> {
const responses = this.requestService.getByUUID(requestId).pipe(
getResponseFromEntry()
);
const errorResponses = responses.pipe(
filter((response: RestResponse) => !response.isSuccessful),
mergeMap((response: ErrorResponse) => observableOf(
new ProcessTaskResponse(
response.isSuccessful,
new RemoteDataError(response.statusCode, response.statusText, response.errorMessage)
))
));
const successResponses = responses.pipe(
filter((response: RestResponse) => response.isSuccessful),
map((response: MessageResponse) => new ProcessTaskResponse(response.isSuccessful)),
distinctUntilChanged()
);
return observableMerge(errorResponses, successResponses);
}
protected getEndpointByIDHref(endpoint, resourceID): string {
return isNotEmpty(resourceID) ? `${endpoint}/${resourceID}` : `${endpoint}`;
}
protected getEndpointByMethod(endpoint: string, method: string): string {
return isNotEmpty(method) ? `${endpoint}/${method}` : `${endpoint}`;
}
public postToEndpoint(linkPath: string, body: any, scopeId?: string, options?: HttpOptions): Observable<ProcessTaskResponse> {
const requestId = this.requestService.generateRequestId();
return this.halService.getEndpoint(linkPath).pipe(
filter((href: string) => isNotEmpty(href)),
map((endpointURL: string) => this.getEndpointByIDHref(endpointURL, scopeId)),
distinctUntilChanged(),
map((endpointURL: string) => new TaskPostRequest(requestId, endpointURL, body, options)),
tap((request: PostRequest) => this.requestService.configure(request)),
flatMap((request: PostRequest) => this.fetchRequest(requestId)),
distinctUntilChanged());
}
public deleteById(linkName: string, scopeId: string, options?: HttpOptions): Observable<ProcessTaskResponse> {
const requestId = this.requestService.generateRequestId();
return this.halService.getEndpoint(linkName || this.linkPath).pipe(
filter((href: string) => isNotEmpty(href)),
distinctUntilChanged(),
map((endpointURL: string) => this.getEndpointByIDHref(endpointURL, scopeId)),
map((endpointURL: string) => new TaskDeleteRequest(requestId, endpointURL, null, options)),
tap((request: DeleteRequest) => this.requestService.configure(request)),
flatMap((request: DeleteRequest) => this.fetchRequest(requestId)),
distinctUntilChanged());
}
protected makeHttpOptions() {
const options: HttpOptions = Object.create({});
let headers = new HttpHeaders();
headers = headers.append('Content-Type', 'application/x-www-form-urlencoded');
options.headers = headers;
return options;
}
}