Merged submission module code

This commit is contained in:
Giuseppe
2018-07-26 18:36:36 +02:00
parent b6e4e2562d
commit 6f60cd68e2
179 changed files with 9143 additions and 77 deletions

View File

@@ -0,0 +1,45 @@
/**
* Combines a variable number of strings representing parts
* of a relative REST URL in to a single, absolute REST URL
*
*/
import { isNotUndefined } from '../../../shared/empty.util';
export interface JsonPatchOperationPathObject {
rootElement: string;
subRootElement: string;
path: string;
}
export class JsonPatchOperationPathCombiner {
private _rootElement: string;
private _subRootElement: string;
constructor(rootElement, ...subRootElements: string[]) {
this._rootElement = rootElement;
this._subRootElement = subRootElements.join('/');
}
get rootElement(): string {
return this._rootElement;
}
get subRootElement(): string {
return this._subRootElement;
}
public getPath(fragment?: string|string[]): JsonPatchOperationPathObject {
if (isNotUndefined(fragment) && Array.isArray(fragment)) {
fragment = fragment.join('/');
}
let path;
if (isNotUndefined(fragment)) {
path = '/' + this._rootElement + '/' + this._subRootElement + '/' + fragment;
} else {
path = '/' + this._rootElement + '/' + this._subRootElement;
}
return {rootElement: this._rootElement, subRootElement: this._subRootElement, path: path};
}
}

View File

@@ -0,0 +1,111 @@
import { Store } from '@ngrx/store';
import { CoreState } from '../../core.reducers';
import {
NewPatchAddOperationAction,
NewPatchRemoveOperationAction,
NewPatchReplaceOperationAction
} from '../json-patch-operations.actions';
import { JsonPatchOperationPathObject } from './json-patch-operation-path-combiner';
import { Injectable } from '@angular/core';
import { isEmpty, isNotEmpty } from '../../../shared/empty.util';
import { dateToGMTString } from '../../../shared/date.util';
import { AuthorityValueModel } from '../../integration/models/authority-value.model';
import { FormFieldMetadataValueObject } from '../../../shared/form/builder/models/form-field-metadata-value.model';
import { FormFieldLanguageValueObject } from '../../../shared/form/builder/models/form-field-language-value.model';
@Injectable()
export class JsonPatchOperationsBuilder {
constructor(private store: Store<CoreState>) {
}
add(path: JsonPatchOperationPathObject, value, first = false, plain = false) {
this.store.dispatch(
new NewPatchAddOperationAction(
path.rootElement,
path.subRootElement,
path.path, this.prepareValue(value, plain, first)));
}
replace(path: JsonPatchOperationPathObject, value, plain = false) {
this.store.dispatch(
new NewPatchReplaceOperationAction(
path.rootElement,
path.subRootElement,
path.path,
this.prepareValue(value, plain, false)));
}
remove(path: JsonPatchOperationPathObject) {
this.store.dispatch(
new NewPatchRemoveOperationAction(
path.rootElement,
path.subRootElement,
path.path));
}
protected prepareValue(value: any, plain: boolean, first: boolean) {
let operationValue: any = null;
if (isNotEmpty(value)) {
if (plain) {
operationValue = value;
} else {
if (Array.isArray(value)) {
operationValue = [];
value.forEach((entry) => {
if ((typeof entry === 'object')) {
operationValue.push(this.prepareObjectValue(entry));
} else {
operationValue.push(new FormFieldMetadataValueObject(entry));
// operationValue.push({value: entry});
// operationValue.push(entry);
}
});
} else if (typeof value === 'object') {
operationValue = this.prepareObjectValue(value);
} else {
operationValue = new FormFieldMetadataValueObject(value);
}
}
}
return (first && !Array.isArray(operationValue)) ? [operationValue] : operationValue;
}
protected prepareObjectValue(value: any) {
let operationValue = Object.create({});
if (isEmpty(value) || value instanceof FormFieldMetadataValueObject) {
operationValue = value;
} else if (value instanceof Date) {
operationValue = new FormFieldMetadataValueObject(dateToGMTString(value));
} else if (value instanceof AuthorityValueModel) {
operationValue = this.prepareAuthorityValue(value);
} else if (value instanceof FormFieldLanguageValueObject) {
operationValue = new FormFieldMetadataValueObject(value.value, value.language);
} else if (value.hasOwnProperty('value')) {
operationValue = new FormFieldMetadataValueObject(value.value);
// operationValue = value;
} else {
Object.keys(value)
.forEach((key) => {
if (typeof value[key] === 'object') {
operationValue[key] = this.prepareObjectValue(value[key]);
} else {
operationValue[key] = value[key];
}
});
// operationValue = {value: value};
}
return operationValue;
}
protected prepareAuthorityValue(value: any) {
let operationValue: any = null;
if (isNotEmpty(value.id)) {
operationValue = new FormFieldMetadataValueObject(value.value, value.language, value.id);
} else {
operationValue = new FormFieldMetadataValueObject(value.value, value.language);
}
return operationValue;
}
}

View File

@@ -0,0 +1,279 @@
import { Action } from '@ngrx/store';
import { type } from '../../shared/ngrx/type';
/**
* For each action type in an action group, make a simple
* enum object for all of this group's action types.
*
* The 'type' utility function coerces strings into string
* literal types and runs a simple check to guarantee all
* action types in the application are unique.
*/
export const JsonPatchOperationsActionTypes = {
NEW_JSON_PATCH_ADD_OPERATION: type('dspace/core/patch/NEW_JSON_PATCH_ADD_OPERATION'),
NEW_JSON_PATCH_COPY_OPERATION: type('dspace/core/patch/NEW_JSON_PATCH_COPY_OPERATION'),
NEW_JSON_PATCH_MOVE_OPERATION: type('dspace/core/patch/NEW_JSON_PATCH_MOVE_OPERATION'),
NEW_JSON_PATCH_REMOVE_OPERATION: type('dspace/core/patch/NEW_JSON_PATCH_REMOVE_OPERATION'),
NEW_JSON_PATCH_REPLACE_OPERATION: type('dspace/core/patch/NEW_JSON_PATCH_REPLACE_OPERATION'),
COMMIT_JSON_PATCH_OPERATIONS: type('dspace/core/patch/COMMIT_JSON_PATCH_OPERATIONS'),
ROLLBACK_JSON_PATCH_OPERATIONS: type('dspace/core/patch/ROLLBACK_JSON_PATCH_OPERATIONS'),
FLUSH_JSON_PATCH_OPERATIONS: type('dspace/core/patch/FLUSH_JSON_PATCH_OPERATIONS'),
START_TRANSACTION_JSON_PATCH_OPERATIONS: type('dspace/core/patch/START_TRANSACTION_JSON_PATCH_OPERATIONS'),
};
/* tslint:disable:max-classes-per-file */
/**
* An ngrx action to commit the current transaction
*/
export class CommitPatchOperationsAction implements Action {
type = JsonPatchOperationsActionTypes.COMMIT_JSON_PATCH_OPERATIONS;
payload: {
resourceType: string;
resourceId: string;
};
/**
* Create a new CommitPatchOperationsAction
*
* @param resourceType
* the resource's type
* @param resourceId
* the resource's ID
*/
constructor(resourceType: string, resourceId: string) {
this.payload = { resourceType, resourceId };
}
}
/**
* An ngrx action to rollback the current transaction
*/
export class RollbacktPatchOperationsAction implements Action {
type = JsonPatchOperationsActionTypes.ROLLBACK_JSON_PATCH_OPERATIONS;
payload: {
resourceType: string;
resourceId: string;
};
/**
* Create a new CommitPatchOperationsAction
*
* @param resourceType
* the resource's type
* @param resourceId
* the resource's ID
*/
constructor(resourceType: string, resourceId: string) {
this.payload = { resourceType, resourceId };
}
}
/**
* An ngrx action to initiate a transaction block
*/
export class StartTransactionPatchOperationsAction implements Action {
type = JsonPatchOperationsActionTypes.START_TRANSACTION_JSON_PATCH_OPERATIONS;
payload: {
resourceType: string;
resourceId: string;
startTime: number;
};
/**
* Create a new CommitPatchOperationsAction
*
* @param resourceType
* the resource's type
* @param resourceId
* the resource's ID
* @param startTime
* the start timestamp
*/
constructor(resourceType: string, resourceId: string, startTime: number) {
this.payload = { resourceType, resourceId, startTime };
}
}
/**
* An ngrx action to flush list of the JSON Patch operations
*/
export class FlushPatchOperationsAction implements Action {
type = JsonPatchOperationsActionTypes.FLUSH_JSON_PATCH_OPERATIONS;
payload: {
resourceType: string;
resourceId: string;
};
/**
* Create a new FlushPatchOperationsAction
*
* @param resourceType
* the resource's type
* @param resourceId
* the resource's ID
*/
constructor(resourceType: string, resourceId: string) {
this.payload = { resourceType, resourceId };
}
}
/**
* An ngrx action to Add new HTTP/PATCH ADD operations to state
*/
export class NewPatchAddOperationAction implements Action {
type = JsonPatchOperationsActionTypes.NEW_JSON_PATCH_ADD_OPERATION;
payload: {
resourceType: string;
resourceId: string;
path: string;
value: any
};
/**
* Create a new NewPatchAddOperationAction
*
* @param resourceType
* the resource's type where to add operation
* @param resourceId
* the resource's ID
* @param path
* the path of the operation
* @param value
* the operation's payload
*/
constructor(resourceType: string, resourceId: string, path: string, value: any) {
this.payload = { resourceType, resourceId, path, value };
}
}
/**
* An ngrx action to add new JSON Patch COPY operation to state
*/
export class NewPatchCopyOperationAction implements Action {
type = JsonPatchOperationsActionTypes.NEW_JSON_PATCH_COPY_OPERATION;
payload: {
resourceType: string;
resourceId: string;
from: string;
path: string;
};
/**
* Create a new NewPatchCopyOperationAction
*
* @param resourceType
* the resource's type
* @param resourceId
* the resource's ID
* @param from
* the path to copy the value from
* @param path
* the path where to copy the value
*/
constructor(resourceType: string, resourceId: string, from: string, path: string) {
this.payload = { resourceType, resourceId, from, path };
}
}
/**
* An ngrx action to Add new JSON Patch MOVE operation to state
*/
export class NewPatchMoveOperationAction implements Action {
type = JsonPatchOperationsActionTypes.NEW_JSON_PATCH_MOVE_OPERATION;
payload: {
resourceType: string;
resourceId: string;
from: string;
path: string;
};
/**
* Create a new NewPatchMoveOperationAction
*
* @param resourceType
* the resource's type
* @param resourceId
* the resource's ID
* @param from
* the path to move the value from
* @param path
* the path where to move the value
*/
constructor(resourceType: string, resourceId: string, from: string, path: string) {
this.payload = { resourceType, resourceId, from, path };
}
}
/**
* An ngrx action to Add new JSON Patch REMOVE operation to state
*/
export class NewPatchRemoveOperationAction implements Action {
type = JsonPatchOperationsActionTypes.NEW_JSON_PATCH_REMOVE_OPERATION;
payload: {
resourceType: string;
resourceId: string;
path: string;
};
/**
* Create a new NewPatchRemoveOperationAction
*
* @param resourceType
* the resource's type
* @param resourceId
* the resource's ID
* @param path
* the path of the operation
*/
constructor(resourceType: string, resourceId: string, path: string) {
this.payload = { resourceType, resourceId, path };
}
}
/**
* An ngrx action to add new JSON Patch REPLACE operation to state
*/
export class NewPatchReplaceOperationAction implements Action {
type = JsonPatchOperationsActionTypes.NEW_JSON_PATCH_REPLACE_OPERATION;
payload: {
resourceType: string;
resourceId: string;
path: string;
value: any
};
/**
* Create a new NewPatchReplaceOperationAction
*
* @param resourceType
* the resource's type
* @param resourceId
* the resource's ID
* @param path
* the path of the operation
* @param value
* the operation's payload
*/
constructor(resourceType: string, resourceId: string, path: string, value: any) {
this.payload = { resourceType, resourceId, path, value };
}
}
/* tslint:enable:max-classes-per-file */
/**
* Export a type alias of all actions in this action group
* so that reducers can easily compose action types
*/
export type PatchOperationsActions
= CommitPatchOperationsAction
| FlushPatchOperationsAction
| NewPatchAddOperationAction
| NewPatchCopyOperationAction
| NewPatchMoveOperationAction
| NewPatchRemoveOperationAction
| NewPatchReplaceOperationAction
| RollbacktPatchOperationsAction
| StartTransactionPatchOperationsAction

View File

@@ -0,0 +1,20 @@
import { Injectable } from '@angular/core';
import { Effect, Actions } from '@ngrx/effects';
import {
CommitPatchOperationsAction, FlushPatchOperationsAction,
JsonPatchOperationsActionTypes
} from './json-patch-operations.actions';
@Injectable()
export class JsonPatchOperationsEffects {
@Effect() commit$ = this.actions$
.ofType(JsonPatchOperationsActionTypes.COMMIT_JSON_PATCH_OPERATIONS)
.map((action: CommitPatchOperationsAction) => {
return new FlushPatchOperationsAction(action.payload.resourceType, action.payload.resourceId);
});
constructor(private actions$: Actions) {}
}

View File

@@ -0,0 +1,292 @@
import { hasValue, isNotEmpty, isNotUndefined, isNull } from '../../shared/empty.util';
import {
FlushPatchOperationsAction,
PatchOperationsActions,
JsonPatchOperationsActionTypes,
NewPatchAddOperationAction,
NewPatchCopyOperationAction,
NewPatchMoveOperationAction,
NewPatchRemoveOperationAction,
NewPatchReplaceOperationAction,
CommitPatchOperationsAction,
StartTransactionPatchOperationsAction,
RollbacktPatchOperationsAction
} from './json-patch-operations.actions';
import { JsonPatchOperationModel, JsonPatchOperationType } from './json-patch.model';
export interface JsonPatchOperationObject {
operation: JsonPatchOperationModel;
timeAdded: number;
}
export interface JsonPatchOperationsEntry {
body: JsonPatchOperationObject[];
}
export interface JsonPatchOperationsResourceEntry {
children: { [resourceId: string]: JsonPatchOperationsEntry };
transactionStartTime: number;
commitPending: boolean;
}
/**
* The JSON patch operations State
*
* Consists of a map with a namespace as key,
* and an array of JsonPatchOperationModel as values
*/
export interface JsonPatchOperationsState {
[resourceType: string]: JsonPatchOperationsResourceEntry;
}
const initialState: JsonPatchOperationsState = Object.create(null);
export function jsonPatchOperationsReducer(state = initialState, action: PatchOperationsActions): JsonPatchOperationsState {
switch (action.type) {
case JsonPatchOperationsActionTypes.COMMIT_JSON_PATCH_OPERATIONS: {
return commitOperations(state, action as CommitPatchOperationsAction);
}
case JsonPatchOperationsActionTypes.FLUSH_JSON_PATCH_OPERATIONS: {
return flushOperation(state, action as FlushPatchOperationsAction);
}
case JsonPatchOperationsActionTypes.NEW_JSON_PATCH_ADD_OPERATION: {
return newOperation(state, action as NewPatchAddOperationAction);
}
case JsonPatchOperationsActionTypes.NEW_JSON_PATCH_COPY_OPERATION: {
return newOperation(state, action as NewPatchCopyOperationAction);
}
case JsonPatchOperationsActionTypes.NEW_JSON_PATCH_MOVE_OPERATION: {
return newOperation(state, action as NewPatchMoveOperationAction);
}
case JsonPatchOperationsActionTypes.NEW_JSON_PATCH_REMOVE_OPERATION: {
return newOperation(state, action as NewPatchRemoveOperationAction);
}
case JsonPatchOperationsActionTypes.NEW_JSON_PATCH_REPLACE_OPERATION: {
return newOperation(state, action as NewPatchReplaceOperationAction);
}
case JsonPatchOperationsActionTypes.ROLLBACK_JSON_PATCH_OPERATIONS: {
return rollbackOperations(state, action as RollbacktPatchOperationsAction);
}
case JsonPatchOperationsActionTypes.START_TRANSACTION_JSON_PATCH_OPERATIONS: {
return startTransactionPatchOperations(state, action as StartTransactionPatchOperationsAction);
}
default: {
return state;
}
}
}
/**
* Set the transaction start time.
*
* @param state
* the current state
* @param action
* an StartTransactionPatchOperationsAction
* @return JsonPatchOperationsState
* the new state.
*/
function startTransactionPatchOperations(state: JsonPatchOperationsState, action: StartTransactionPatchOperationsAction): JsonPatchOperationsState {
if (hasValue(state[ action.payload.resourceType ])
&& isNull(state[ action.payload.resourceType ].transactionStartTime)) {
return Object.assign({}, state, {
[action.payload.resourceType]: Object.assign({}, state[ action.payload.resourceType ], {
children: state[ action.payload.resourceType ].children,
transactionStartTime: action.payload.startTime,
commitPending: true
})
});
} else {
return state;
}
}
/**
* Set commit pending state.
*
* @param state
* the current state
* @param action
* an CommitPatchOperationsAction
* @return JsonPatchOperationsState
* the new state, with the section new validity status.
*/
function commitOperations(state: JsonPatchOperationsState, action: CommitPatchOperationsAction): JsonPatchOperationsState {
if (hasValue(state[ action.payload.resourceType ])
&& state[ action.payload.resourceType ].commitPending) {
return Object.assign({}, state, {
[action.payload.resourceType]: Object.assign({}, state[ action.payload.resourceType ], {
children: state[ action.payload.resourceType ].children,
transactionStartTime: state[ action.payload.resourceType ].transactionStartTime,
commitPending: false
})
});
} else {
return state;
}
}
/**
* Set commit pending state.
*
* @param state
* the current state
* @param action
* an RollbacktPatchOperationsAction
* @return JsonPatchOperationsState
* the new state.
*/
function rollbackOperations(state: JsonPatchOperationsState, action: RollbacktPatchOperationsAction): JsonPatchOperationsState {
if (hasValue(state[ action.payload.resourceType ])
&& state[ action.payload.resourceType ].commitPending) {
return Object.assign({}, state, {
[action.payload.resourceType]: Object.assign({}, state[ action.payload.resourceType ], {
children: state[ action.payload.resourceType ].children,
transactionStartTime: null,
commitPending: false
})
});
} else {
return state;
}
}
/**
* Add new JSON patch operation list.
*
* @param state
* the current state
* @param action
* an NewPatchAddOperationAction
* @return JsonPatchOperationsState
* the new state, with the section new validity status.
*/
function newOperation(state: JsonPatchOperationsState, action): JsonPatchOperationsState {
const newState = Object.assign({}, state);
const newBody = addOperationToList(
(hasValue(newState[ action.payload.resourceType ])
&& hasValue(newState[ action.payload.resourceType ].children)
&& hasValue(newState[ action.payload.resourceType ].children[ action.payload.resourceId ])
&& isNotEmpty(newState[ action.payload.resourceType ].children[ action.payload.resourceId ].body))
? newState[ action.payload.resourceType ].children[ action.payload.resourceId ].body : Array.of(),
action.type,
action.payload.path,
hasValue(action.payload.value) ? action.payload.value : null);
if (hasValue(newState[ action.payload.resourceType ])
&& hasValue(newState[ action.payload.resourceType ].children)) {
return Object.assign({}, state, {
[action.payload.resourceType]: Object.assign({}, state[ action.payload.resourceType ], {
children: Object.assign({}, state[ action.payload.resourceType ].children, {
[action.payload.resourceId]: {
body: newBody,
}
}),
transactionStartTime: state[ action.payload.resourceType ].transactionStartTime,
commitPending: isNotUndefined(state[ action.payload.resourceType ].commitPending) ? state[ action.payload.resourceType ].commitPending : false
})
});
} else {
return Object.assign({}, state, {
[action.payload.resourceType]: Object.assign({}, {
children: {
[action.payload.resourceId]: {
body: newBody,
}
},
transactionStartTime: null,
commitPending: false
})
});
}
}
/**
* Set the section validity.
*
* @param state
* the current state
* @param action
* an LoadSubmissionFormAction
* @return SubmissionObjectState
* the new state, with the section new validity status.
*/
function flushOperation(state: JsonPatchOperationsState, action: FlushPatchOperationsAction): JsonPatchOperationsState {
if (hasValue(state[ action.payload.resourceType ])) {
let newChildren;
if (isNotUndefined(action.payload.resourceId)) {
// flush only specified child's operations
if (hasValue(state[ action.payload.resourceType ].children)
&& hasValue(state[ action.payload.resourceType ].children[ action.payload.resourceId ])) {
newChildren = Object.assign({}, state[ action.payload.resourceType ].children, {
[action.payload.resourceId]: {
body: state[ action.payload.resourceType ].children[ action.payload.resourceId ].body
.filter((entry) => entry.timeAdded > state[ action.payload.resourceType ].transactionStartTime)
}
});
} else {
newChildren = state[ action.payload.resourceType ].children;
}
} else {
// flush all children's operations
newChildren = state[ action.payload.resourceType ].children;
Object.keys(newChildren)
.forEach((resourceId) => {
newChildren = Object.assign({}, newChildren, {
[resourceId]: {
body: newChildren[ resourceId ].body
.filter((entry) => entry.timeAdded > state[ action.payload.resourceType ].transactionStartTime)
}
});
})
}
return Object.assign({}, state, {
[action.payload.resourceType]: Object.assign({}, state[ action.payload.resourceType ], {
children: newChildren,
transactionStartTime: null,
commitPending: state[ action.payload.resourceType ].commitPending
})
});
} else {
return state;
}
}
function addOperationToList(body: JsonPatchOperationObject[], actionType, targetPath, value?) {
const newBody = Array.from(body);
switch (actionType) {
case JsonPatchOperationsActionTypes.NEW_JSON_PATCH_ADD_OPERATION:
newBody.push(makeOperationEntry({
op: JsonPatchOperationType.add,
path: targetPath,
value: value
}));
break;
case JsonPatchOperationsActionTypes.NEW_JSON_PATCH_REPLACE_OPERATION:
newBody.push(makeOperationEntry({
op: JsonPatchOperationType.replace,
path: targetPath,
value: value
}));
break;
case JsonPatchOperationsActionTypes.NEW_JSON_PATCH_REMOVE_OPERATION:
newBody.push(makeOperationEntry({ op: JsonPatchOperationType.remove, path: targetPath }));
break;
}
return newBody;
}
function makeOperationEntry(operation) {
return { operation: operation, timeAdded: new Date().getTime() };
}

View File

@@ -0,0 +1,127 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { hasValue, isEmpty, isNotEmpty, isNotUndefined, isUndefined } from '../../shared/empty.util';
import { ErrorResponse, PostPatchSuccessResponse, RestResponse } from '../cache/response-cache.models';
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
import { ResponseCacheService } from '../cache/response-cache.service';
import { PatchRequest, RestRequest, SubmissionPatchRequest } from '../data/request.models';
import { RequestService } from '../data/request.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { CoreState } from '../core.reducers';
import { Store } from '@ngrx/store';
import { jsonPatchOperationsByResourceType } from './selectors';
import { JsonPatchOperationsResourceEntry } from './json-patch-operations.reducer';
import {
CommitPatchOperationsAction,
RollbacktPatchOperationsAction,
StartTransactionPatchOperationsAction
} from './json-patch-operations.actions';
import { JsonPatchOperationModel } from './json-patch.model';
@Injectable()
export class JsonPatchOperationsService<ResponseDefinitionDomain> {
protected linkPath;
constructor(protected responseCache: ResponseCacheService,
protected requestService: RequestService,
protected store: Store<CoreState>,
protected halService: HALEndpointService) {
}
protected submitData(request: RestRequest): Observable<ResponseDefinitionDomain> {
const [successResponse, errorResponse] = this.responseCache.get(request.href)
.map((entry: ResponseCacheEntry) => entry.response)
.partition((response: RestResponse) => response.isSuccessful);
return Observable.merge(
errorResponse.flatMap((response: ErrorResponse) =>
Observable.throw(new Error(`Couldn't send data to server`))),
successResponse
.filter((response: PostPatchSuccessResponse) => isNotEmpty(response))
.map((response: PostPatchSuccessResponse) => response.dataDefinition)
.distinctUntilChanged());
}
protected submitJsonPatchOperations(hrefObs: Observable<string>, resourceType: string, resourceId?: string) {
let startTransactionTime = null;
const [patchRequestObs, emptyRequestObs] = hrefObs
.flatMap((endpointURL: string) => {
return this.store.select(jsonPatchOperationsByResourceType(resourceType))
.take(1)
.filter((operationsList: JsonPatchOperationsResourceEntry) => isUndefined(operationsList) || !(operationsList.commitPending))
.do(() => startTransactionTime = new Date().getTime())
.map((operationsList: JsonPatchOperationsResourceEntry) => {
const body: JsonPatchOperationModel[] = [];
if (isNotEmpty(operationsList)) {
if (isNotEmpty(resourceId)) {
if (isNotUndefined(operationsList.children[resourceId]) && isNotEmpty(operationsList.children[resourceId].body)) {
operationsList.children[resourceId].body.forEach((entry) => {
body.push(entry.operation);
});
}
} else {
Object.keys(operationsList.children)
.filter((key) => operationsList.children.hasOwnProperty(key))
.filter((key) => hasValue(operationsList.children[key]))
.filter((key) => hasValue(operationsList.children[key].body))
.forEach((key) => {
operationsList.children[key].body.forEach((entry) => {
body.push(entry.operation);
});
})
}
}
return new SubmissionPatchRequest(this.requestService.generateRequestId(), endpointURL, body);
});
})
.partition((request: PatchRequest) => isNotEmpty(request.body));
return Observable.merge(
emptyRequestObs
.filter((request: PatchRequest) => isEmpty(request.body))
.do(() => startTransactionTime = null)
.map(() => null),
patchRequestObs
.filter((request: PatchRequest) => isNotEmpty(request.body))
.do(() => this.store.dispatch(new StartTransactionPatchOperationsAction(resourceType, resourceId, startTransactionTime)))
.do((request: PatchRequest) => this.requestService.configure(request, true))
.flatMap((request: PatchRequest) => {
const [successResponse, errorResponse] = this.responseCache.get(request.href)
.filter((entry: ResponseCacheEntry) => startTransactionTime < entry.timeAdded)
.take(1)
.map((entry: ResponseCacheEntry) => entry.response)
.partition((response: RestResponse) => response.isSuccessful);
return Observable.merge(
errorResponse
.do(() => this.store.dispatch(new RollbacktPatchOperationsAction(resourceType, resourceId)))
.flatMap((response: ErrorResponse) => Observable.of(new Error(`Couldn't patch operations`))),
successResponse
.filter((response: PostPatchSuccessResponse) => isNotEmpty(response))
.do(() => this.store.dispatch(new CommitPatchOperationsAction(resourceType, resourceId)))
.map((response: PostPatchSuccessResponse) => response.dataDefinition)
.distinctUntilChanged());
})
);
}
protected getEndpointByIDHref(endpoint, resourceID): string {
return isNotEmpty(resourceID) ? `${endpoint}/${resourceID}` : `${endpoint}`;
}
public jsonPatchByResourceType(linkName: string, scopeId: string, resourceType: string,) {
const hrefObs = this.halService.getEndpoint(linkName)
.filter((href: string) => isNotEmpty(href))
.distinctUntilChanged()
.map((endpointURL: string) => this.getEndpointByIDHref(endpointURL, scopeId));
return this.submitJsonPatchOperations(hrefObs, resourceType);
}
public jsonPatchByResourceID(linkName: string, scopeId: string, resourceType: string, resourceId: string) {
const hrefObs = this.halService.getEndpoint(linkName)
.filter((href: string) => isNotEmpty(href))
.distinctUntilChanged()
.map((endpointURL: string) => this.getEndpointByIDHref(endpointURL, scopeId));
return this.submitJsonPatchOperations(hrefObs, resourceType, resourceId);
}
}

View File

@@ -0,0 +1,14 @@
export enum JsonPatchOperationType {
test = 'test',
remove = 'remove',
add = 'add',
replace = 'replace',
move = 'move',
copy = 'copy',
}
export class JsonPatchOperationModel {
op: JsonPatchOperationType;
path: string;
value: any;
}

View File

@@ -0,0 +1,34 @@
// @TODO: Merge with keySelector function present in 'src/app/core/shared/selectors.ts'
import { createSelector, MemoizedSelector, Selector } from '@ngrx/store';
import { hasValue } from '../../shared/empty.util';
import { coreSelector, CoreState } from '../core.reducers';
import { JsonPatchOperationsEntry, JsonPatchOperationsResourceEntry } from './json-patch-operations.reducer';
export function keySelector<T, V>(parentSelector: Selector<any, any>, subState: string, key: string): MemoizedSelector<T, V> {
return createSelector(parentSelector, (state: T) => {
if (hasValue(state[subState])) {
return state[subState][key];
} else {
return undefined;
}
});
}
export function subStateSelector<T, V>(parentSelector: Selector<any, any>, subState: string): MemoizedSelector<T, V> {
return createSelector(parentSelector, (state: T) => {
if (hasValue(state[subState])) {
return state[subState];
} else {
return undefined;
}
});
}
export function jsonPatchOperationsByResourceType(resourceType: string): MemoizedSelector<CoreState, JsonPatchOperationsResourceEntry> {
return keySelector<CoreState, JsonPatchOperationsResourceEntry>(coreSelector,'json/patch', resourceType);
}
export function jsonPatchOperationsByResourcId(resourceType: string, resourceId: string): MemoizedSelector<CoreState, JsonPatchOperationsEntry> {
const resourceTypeSelector = jsonPatchOperationsByResourceType(resourceType);
return subStateSelector<CoreState, JsonPatchOperationsEntry>(resourceTypeSelector, resourceId);
}