mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-17 06:53:03 +00:00
Merged submission module code
This commit is contained in:
@@ -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};
|
||||
}
|
||||
}
|
111
src/app/core/json-patch/builder/json-patch-operations-builder.ts
Normal file
111
src/app/core/json-patch/builder/json-patch-operations-builder.ts
Normal 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;
|
||||
}
|
||||
|
||||
}
|
279
src/app/core/json-patch/json-patch-operations.actions.ts
Normal file
279
src/app/core/json-patch/json-patch-operations.actions.ts
Normal 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
|
20
src/app/core/json-patch/json-patch-operations.effects.ts
Normal file
20
src/app/core/json-patch/json-patch-operations.effects.ts
Normal 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) {}
|
||||
|
||||
}
|
292
src/app/core/json-patch/json-patch-operations.reducer.ts
Normal file
292
src/app/core/json-patch/json-patch-operations.reducer.ts
Normal 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() };
|
||||
}
|
127
src/app/core/json-patch/json-patch-operations.service.ts
Normal file
127
src/app/core/json-patch/json-patch-operations.service.ts
Normal 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);
|
||||
}
|
||||
}
|
14
src/app/core/json-patch/json-patch.model.ts
Normal file
14
src/app/core/json-patch/json-patch.model.ts
Normal 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;
|
||||
}
|
34
src/app/core/json-patch/selectors.ts
Normal file
34
src/app/core/json-patch/selectors.ts
Normal 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);
|
||||
}
|
Reference in New Issue
Block a user