1
0

73014: Create patch from object-updates and send immediate patch for item-metadata-edit

This commit is contained in:
Kristof De Langhe
2020-09-11 17:34:37 +02:00
parent 4b11e1d9cd
commit 1647c95600
10 changed files with 114 additions and 28 deletions

View File

@@ -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);
}
/**

View File

@@ -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;

View File

@@ -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));
}
}
/**

View File

@@ -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

View File

@@ -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 };
}
}

View File

@@ -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 });

View File

@@ -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;
})
);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}

View File

@@ -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);
}