forked from hazza/dspace-angular
73014: Create patch from object-updates and send immediate patch for item-metadata-edit
This commit is contained in:
@@ -11,6 +11,7 @@ import { NgModel } from '@angular/forms';
|
||||
import { MetadatumViewModel } from '../../../../core/shared/metadata.models';
|
||||
import { MetadataField } from '../../../../core/metadata/metadata-field.model';
|
||||
import { InputSuggestion } from '../../../../shared/input-suggestions/input-suggestions.model';
|
||||
import { METADATA_PATCH_OPERATION_SERVICE_TOKEN } from '../../../../core/data/object-updates/patch-operation-service/metadata-patch-operation.service';
|
||||
|
||||
@Component({
|
||||
// tslint:disable-next-line:component-selector
|
||||
@@ -75,7 +76,7 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges {
|
||||
* Sends a new change update for this field to the object updates service
|
||||
*/
|
||||
update(ngModel?: NgModel) {
|
||||
this.objectUpdatesService.saveChangeFieldUpdate(this.url, cloneDeep(this.metadata));
|
||||
this.objectUpdatesService.saveChangeFieldUpdate(this.url, cloneDeep(this.metadata), METADATA_PATCH_OPERATION_SERVICE_TOKEN);
|
||||
if (hasValue(ngModel)) {
|
||||
this.checkValidity(ngModel);
|
||||
}
|
||||
@@ -103,7 +104,7 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges {
|
||||
* Sends a new remove update for this field to the object updates service
|
||||
*/
|
||||
remove() {
|
||||
this.objectUpdatesService.saveRemoveFieldUpdate(this.url, cloneDeep(this.metadata));
|
||||
this.objectUpdatesService.saveRemoveFieldUpdate(this.url, cloneDeep(this.metadata), METADATA_PATCH_OPERATION_SERVICE_TOKEN);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -17,8 +17,12 @@ import { Metadata } from '../../../core/shared/metadata.utils';
|
||||
import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component';
|
||||
import { MetadataField } from '../../../core/metadata/metadata-field.model';
|
||||
import { UpdateDataService } from '../../../core/data/update-data.service';
|
||||
import { hasNoValue, hasValue } from '../../../shared/empty.util';
|
||||
import { hasNoValue, hasValue, isNotEmpty } from '../../../shared/empty.util';
|
||||
import { AlertType } from '../../../shared/alert/aletr-type';
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import { METADATA_PATCH_OPERATION_SERVICE_TOKEN } from '../../../core/data/object-updates/patch-operation-service/metadata-patch-operation.service';
|
||||
import { DSOSuccessResponse } from '../../../core/cache/response.models';
|
||||
import { ObjectCacheService } from '../../../core/cache/object-cache.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-metadata',
|
||||
@@ -55,6 +59,7 @@ export class ItemMetadataComponent extends AbstractItemUpdateComponent {
|
||||
public translateService: TranslateService,
|
||||
public route: ActivatedRoute,
|
||||
public metadataFieldService: RegistryService,
|
||||
public objectCacheService: ObjectCacheService,
|
||||
) {
|
||||
super(itemService, objectUpdatesService, router, notificationsService, translateService, route);
|
||||
}
|
||||
@@ -89,7 +94,7 @@ export class ItemMetadataComponent extends AbstractItemUpdateComponent {
|
||||
* @param metadata The metadata to add, if no parameter is supplied, create a new Metadatum
|
||||
*/
|
||||
add(metadata: MetadatumViewModel = new MetadatumViewModel()) {
|
||||
this.objectUpdatesService.saveAddFieldUpdate(this.url, metadata);
|
||||
this.objectUpdatesService.saveAddFieldUpdate(this.url, metadata, METADATA_PATCH_OPERATION_SERVICE_TOKEN);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,15 +111,20 @@ export class ItemMetadataComponent extends AbstractItemUpdateComponent {
|
||||
public submit() {
|
||||
this.isValid().pipe(first()).subscribe((isValid) => {
|
||||
if (isValid) {
|
||||
const metadata$: Observable<Identifiable[]> = this.objectUpdatesService.getUpdatedFields(this.url, this.item.metadataAsList) as Observable<MetadatumViewModel[]>;
|
||||
metadata$.pipe(
|
||||
this.objectUpdatesService.createPatch(this.url).pipe(
|
||||
first(),
|
||||
switchMap((metadata: MetadatumViewModel[]) => {
|
||||
const updatedItem: Item = Object.assign(cloneDeep(this.item), { metadata: Metadata.toMetadataMap(metadata) });
|
||||
return this.updateService.update(updatedItem);
|
||||
}),
|
||||
tap(() => this.updateService.commitUpdates()),
|
||||
getSucceededRemoteData()
|
||||
switchMap((patch: Operation[]) => {
|
||||
return this.updateService.patch(this.item, patch).pipe(
|
||||
switchMap((response: DSOSuccessResponse) => {
|
||||
if (isNotEmpty(response.resourceSelfLinks)) {
|
||||
const selfLink = response.resourceSelfLinks[0];
|
||||
this.objectCacheService.addPatch(selfLink, patch, false);
|
||||
return this.itemService.findByHref(selfLink);
|
||||
}
|
||||
}),
|
||||
getSucceededRemoteData()
|
||||
);
|
||||
})
|
||||
).subscribe(
|
||||
(rd: RemoteData<Item>) => {
|
||||
this.item = rd.payload;
|
||||
|
6
src/app/core/cache/object-cache.service.ts
vendored
6
src/app/core/cache/object-cache.service.ts
vendored
@@ -275,9 +275,11 @@ export class ObjectCacheService {
|
||||
* @param {Operation[]} patch
|
||||
* list of operations to perform
|
||||
*/
|
||||
public addPatch(selfLink: string, patch: Operation[]) {
|
||||
public addPatch(selfLink: string, patch: Operation[], addToSSD = true) {
|
||||
this.store.dispatch(new AddPatchObjectCacheAction(selfLink, patch));
|
||||
this.store.dispatch(new AddToSSBAction(selfLink, RestRequestMethod.PATCH));
|
||||
if (addToSSD) {
|
||||
this.store.dispatch(new AddToSSBAction(selfLink, RestRequestMethod.PATCH));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -20,6 +20,7 @@ import { switchMap, map } from 'rxjs/operators';
|
||||
import { BundleDataService } from './bundle-data.service';
|
||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { RestResponse } from '../cache/response.models';
|
||||
import { Operation } from 'fast-json-patch';
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
/**
|
||||
@@ -165,6 +166,10 @@ export class ItemTemplateDataService implements UpdateDataService<Item> {
|
||||
return this.dataService.update(object);
|
||||
}
|
||||
|
||||
patch(dso: Item, operations: Operation[]): Observable<RestResponse> {
|
||||
return this.dataService.patch(dso, operations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an item template by collection ID
|
||||
* @param collectionID
|
||||
|
@@ -2,6 +2,8 @@ import {type} from '../../../shared/ngrx/type';
|
||||
import {Action} from '@ngrx/store';
|
||||
import {Identifiable} from './object-updates.reducer';
|
||||
import {INotification} from '../../../shared/notifications/models/notification.model';
|
||||
import { InjectionToken } from '@angular/core';
|
||||
import { PatchOperationService } from './patch-operation-service/patch-operation.service';
|
||||
|
||||
/**
|
||||
* The list of ObjectUpdatesAction type definitions
|
||||
@@ -70,6 +72,7 @@ export class AddFieldUpdateAction implements Action {
|
||||
url: string,
|
||||
field: Identifiable,
|
||||
changeType: FieldChangeType,
|
||||
patchOperationServiceToken?: InjectionToken<PatchOperationService<Identifiable>>
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -83,8 +86,9 @@ export class AddFieldUpdateAction implements Action {
|
||||
constructor(
|
||||
url: string,
|
||||
field: Identifiable,
|
||||
changeType: FieldChangeType) {
|
||||
this.payload = { url, field, changeType };
|
||||
changeType: FieldChangeType,
|
||||
patchOperationServiceToken?: InjectionToken<PatchOperationService<Identifiable>>) {
|
||||
this.payload = { url, field, changeType, patchOperationServiceToken };
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -14,6 +14,8 @@ import {
|
||||
} from './object-updates.actions';
|
||||
import { hasNoValue, hasValue } from '../../../shared/empty.util';
|
||||
import {Relationship} from '../../shared/item-relationships/relationship.model';
|
||||
import { InjectionToken } from '@angular/core';
|
||||
import { PatchOperationService } from './patch-operation-service/patch-operation.service';
|
||||
|
||||
/**
|
||||
* Path where discarded objects are saved
|
||||
@@ -48,7 +50,8 @@ export interface Identifiable {
|
||||
*/
|
||||
export interface FieldUpdate {
|
||||
field: Identifiable,
|
||||
changeType: FieldChangeType
|
||||
changeType: FieldChangeType,
|
||||
patchOperationServiceToken?: InjectionToken<PatchOperationService<Identifiable>>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,6 +187,7 @@ function addFieldUpdate(state: any, action: AddFieldUpdateAction) {
|
||||
const url: string = action.payload.url;
|
||||
const field: Identifiable = action.payload.field;
|
||||
const changeType: FieldChangeType = action.payload.changeType;
|
||||
const patchOperationServiceToken: InjectionToken<PatchOperationService<Identifiable>> = action.payload.patchOperationServiceToken;
|
||||
const pageState: ObjectUpdatesEntry = state[url] || {};
|
||||
|
||||
let states = pageState.fieldStates;
|
||||
@@ -194,7 +198,7 @@ function addFieldUpdate(state: any, action: AddFieldUpdateAction) {
|
||||
let fieldUpdate: any = pageState.fieldUpdates[field.uuid] || {};
|
||||
const newChangeType = determineChangeType(fieldUpdate.changeType, changeType);
|
||||
|
||||
fieldUpdate = Object.assign({}, { field, changeType: newChangeType });
|
||||
fieldUpdate = Object.assign({}, { field, changeType: newChangeType, patchOperationServiceToken });
|
||||
|
||||
const fieldUpdates = Object.assign({}, pageState.fieldUpdates, { [field.uuid]: fieldUpdate });
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Injectable, InjectionToken, Injector } from '@angular/core';
|
||||
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
|
||||
import { CoreState } from '../../core.reducers';
|
||||
import { coreSelector } from '../../core.selectors';
|
||||
@@ -26,6 +26,8 @@ import {
|
||||
import { distinctUntilChanged, filter, map, switchMap } from 'rxjs/operators';
|
||||
import { hasNoValue, hasValue, isEmpty, isNotEmpty, isNotEmptyOperator } from '../../../shared/empty.util';
|
||||
import { INotification } from '../../../shared/notifications/models/notification.model';
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import { PatchOperationService } from './patch-operation-service/patch-operation.service';
|
||||
|
||||
function objectUpdatesStateSelector(): MemoizedSelector<CoreState, ObjectUpdatesState> {
|
||||
return createSelector(coreSelector, (state: CoreState) => state['cache/object-updates']);
|
||||
@@ -48,7 +50,8 @@ function virtualMetadataSourceSelector(url: string, source: string): MemoizedSel
|
||||
*/
|
||||
@Injectable()
|
||||
export class ObjectUpdatesService {
|
||||
constructor(private store: Store<CoreState>) {
|
||||
constructor(private store: Store<CoreState>,
|
||||
private injector: Injector) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,8 +70,8 @@ export class ObjectUpdatesService {
|
||||
* @param field An updated field for the page's object
|
||||
* @param changeType The last type of change applied to this field
|
||||
*/
|
||||
private saveFieldUpdate(url: string, field: Identifiable, changeType: FieldChangeType) {
|
||||
this.store.dispatch(new AddFieldUpdateAction(url, field, changeType))
|
||||
private saveFieldUpdate(url: string, field: Identifiable, changeType: FieldChangeType, patchOperationServiceToken?: InjectionToken<PatchOperationService<Identifiable>>) {
|
||||
this.store.dispatch(new AddFieldUpdateAction(url, field, changeType, patchOperationServiceToken))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,8 +188,8 @@ export class ObjectUpdatesService {
|
||||
* @param url The page's URL for which the changes are saved
|
||||
* @param field An updated field for the page's object
|
||||
*/
|
||||
saveAddFieldUpdate(url: string, field: Identifiable) {
|
||||
this.saveFieldUpdate(url, field, FieldChangeType.ADD);
|
||||
saveAddFieldUpdate(url: string, field: Identifiable, patchOperationServiceToken?: InjectionToken<PatchOperationService<Identifiable>>) {
|
||||
this.saveFieldUpdate(url, field, FieldChangeType.ADD, patchOperationServiceToken);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,8 +197,8 @@ export class ObjectUpdatesService {
|
||||
* @param url The page's URL for which the changes are saved
|
||||
* @param field An updated field for the page's object
|
||||
*/
|
||||
saveRemoveFieldUpdate(url: string, field: Identifiable) {
|
||||
this.saveFieldUpdate(url, field, FieldChangeType.REMOVE);
|
||||
saveRemoveFieldUpdate(url: string, field: Identifiable, patchOperationServiceToken?: InjectionToken<PatchOperationService<Identifiable>>) {
|
||||
this.saveFieldUpdate(url, field, FieldChangeType.REMOVE, patchOperationServiceToken);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -203,8 +206,8 @@ export class ObjectUpdatesService {
|
||||
* @param url The page's URL for which the changes are saved
|
||||
* @param field An updated field for the page's object
|
||||
*/
|
||||
saveChangeFieldUpdate(url: string, field: Identifiable) {
|
||||
this.saveFieldUpdate(url, field, FieldChangeType.UPDATE);
|
||||
saveChangeFieldUpdate(url: string, field: Identifiable, patchOperationServiceToken?: InjectionToken<PatchOperationService<Identifiable>>) {
|
||||
this.saveFieldUpdate(url, field, FieldChangeType.UPDATE, patchOperationServiceToken);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -339,4 +342,23 @@ export class ObjectUpdatesService {
|
||||
getLastModified(url: string): Observable<Date> {
|
||||
return this.getObjectEntry(url).pipe(map((entry: ObjectUpdatesEntry) => entry.lastModified));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a patch from the current object-updates state
|
||||
* @param url The URL of the page for which the patch should be created
|
||||
*/
|
||||
createPatch(url: string): Observable<Operation[]> {
|
||||
return this.getObjectEntry(url).pipe(
|
||||
map((entry) => {
|
||||
const patch = [];
|
||||
Object.keys(entry.fieldUpdates).forEach((uuid) => {
|
||||
const update = entry.fieldUpdates[uuid];
|
||||
if (hasValue(update.patchOperationServiceToken)) {
|
||||
patch.push(this.injector.get(update.patchOperationServiceToken).fieldUpdateToPatchOperation(update));
|
||||
}
|
||||
});
|
||||
return patch;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,29 @@
|
||||
import { PatchOperationService } from './patch-operation.service';
|
||||
import { MetadataValue, MetadatumViewModel } from '../../../shared/metadata.models';
|
||||
import { FieldUpdate } from '../object-updates.reducer';
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import { FieldChangeType } from '../object-updates.actions';
|
||||
import { InjectionToken } from '@angular/core';
|
||||
|
||||
export const METADATA_PATCH_OPERATION_SERVICE_TOKEN = new InjectionToken<MetadataPatchOperationService>('MetadataPatchOperationService', {
|
||||
providedIn: 'root',
|
||||
factory: () => new MetadataPatchOperationService(),
|
||||
});
|
||||
|
||||
export class MetadataPatchOperationService implements PatchOperationService<MetadatumViewModel> {
|
||||
fieldUpdateToPatchOperation(fieldUpdate: FieldUpdate): Operation {
|
||||
const metadatum = fieldUpdate.field as MetadatumViewModel;
|
||||
const path = `/metadata/${metadatum.key}`;
|
||||
const val = {
|
||||
value: metadatum.value,
|
||||
language: metadatum.language
|
||||
}
|
||||
|
||||
switch (fieldUpdate.changeType) {
|
||||
case FieldChangeType.ADD: return { op: 'add', path, value: [ val ] };
|
||||
case FieldChangeType.REMOVE: return { op: 'remove', path: `${path}/${metadatum.place}` };
|
||||
case FieldChangeType.UPDATE: return { op: 'replace', path: `${path}/${metadatum.place}`, value: val };
|
||||
default: return undefined;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
import { FieldUpdate, Identifiable } from '../object-updates.reducer';
|
||||
import { Operation } from 'fast-json-patch';
|
||||
|
||||
export interface PatchOperationService<T extends Identifiable> {
|
||||
fieldUpdateToPatchOperation(fieldUpdate: FieldUpdate): Operation;
|
||||
}
|
@@ -1,11 +1,14 @@
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { RestRequestMethod } from './rest-request-method';
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import { RestResponse } from '../cache/response.models';
|
||||
|
||||
/**
|
||||
* Represents a data service to update a given object
|
||||
*/
|
||||
export interface UpdateDataService<T> {
|
||||
patch(dso: T, operations: Operation[]): Observable<RestResponse>;
|
||||
update(object: T): Observable<RemoteData<T>>;
|
||||
commitUpdates(method?: RestRequestMethod);
|
||||
}
|
||||
|
Reference in New Issue
Block a user