64961: Edit bitstream metadata PATCH requests

This commit is contained in:
Kristof De Langhe
2019-09-13 14:39:40 +02:00
parent 91c6b76230
commit 1d3ada20f9
7 changed files with 78 additions and 17 deletions

View File

@@ -91,6 +91,8 @@
"bitstream.edit.form.fileName.label": "Filename", "bitstream.edit.form.fileName.label": "Filename",
"bitstream.edit.form.fileName.hint": "Change the filename for the bitstream. Note that this will change the display bitstream URL, but old links will still resolve as long as the sequence ID does not change.", "bitstream.edit.form.fileName.hint": "Change the filename for the bitstream. Note that this will change the display bitstream URL, but old links will still resolve as long as the sequence ID does not change.",
"bitstream.edit.form.primaryBitstream.label": "Primary bitstream", "bitstream.edit.form.primaryBitstream.label": "Primary bitstream",
"bitstream.edit.notifications.saved.content": "Your changes to this bitstream were saved.",
"bitstream.edit.notifications.saved.title": "Bitstream saved",
"browse.comcol.by.author": "By Author", "browse.comcol.by.author": "By Author",
"browse.comcol.by.dateissued": "By Issue Date", "browse.comcol.by.dateissued": "By Issue Date",
"browse.comcol.by.subject": "By Subject", "browse.comcol.by.subject": "By Subject",

View File

@@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
import { Bitstream } from '../../core/shared/bitstream.model'; import { Bitstream } from '../../core/shared/bitstream.model';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { map } from 'rxjs/operators'; import { map, tap } from 'rxjs/operators';
import { Subscription } from 'rxjs/internal/Subscription'; import { Subscription } from 'rxjs/internal/Subscription';
import { import {
DynamicFormControlModel, DynamicFormGroupModel, DynamicFormLayout, DynamicFormService, DynamicFormControlModel, DynamicFormGroupModel, DynamicFormLayout, DynamicFormService,
@@ -11,6 +11,11 @@ import {
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { DynamicCustomSwitchModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model'; import { DynamicCustomSwitchModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model';
import { cloneDeep } from 'lodash';
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
import { getSucceededRemoteData } from '../../core/shared/operators';
import { RemoteData } from '../../core/data/remote-data';
import { NotificationsService } from '../../shared/notifications/notifications.service';
@Component({ @Component({
selector: 'ds-edit-bitstream-page', selector: 'ds-edit-bitstream-page',
@@ -43,6 +48,11 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
*/ */
HINT_KEY_SUFFIX = '.hint'; HINT_KEY_SUFFIX = '.hint';
/**
* @type {string} Key prefix used to generate notification messages
*/
NOTIFICATIONS_PREFIX = 'bitstream.edit.notifications.';
/** /**
* The Dynamic Input Model for the file's name * The Dynamic Input Model for the file's name
*/ */
@@ -167,7 +177,9 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
constructor(private route: ActivatedRoute, constructor(private route: ActivatedRoute,
private formService: DynamicFormService, private formService: DynamicFormService,
private translate: TranslateService) { private translate: TranslateService,
private bitstreamService: BitstreamDataService,
private notificationsService: NotificationsService) {
} }
/** /**
@@ -232,8 +244,32 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
* Check for changes against the bitstream and send update requests to the REST API * Check for changes against the bitstream and send update requests to the REST API
*/ */
onSubmit() { onSubmit() {
// TODO: Check for changes against the bitstream and send requests to the REST API accordingly const updatedValues = this.formGroup.getRawValue();
console.log(this.formGroup.getRawValue()); const newBitstream = this.formToBitstream(updatedValues);
this.bitstreamService.update(newBitstream).pipe(
tap(() => this.bitstreamService.commitUpdates()),
getSucceededRemoteData()
).subscribe((bitstreamRD: RemoteData<Bitstream>) => {
this.bitstream = bitstreamRD.payload;
this.updateForm(this.bitstream);
this.notificationsService.success(
this.translate.instant(this.NOTIFICATIONS_PREFIX + 'saved.title'),
this.translate.instant(this.NOTIFICATIONS_PREFIX + 'saved.content')
);
});
}
/**
* Parse form data to an updated bitstream object
* @param rawForm Raw form data
*/
formToBitstream(rawForm): Bitstream {
const newBitstream = cloneDeep(this.bitstream);
// TODO: Set bitstream to primary when supported
const primary = rawForm.fileNamePrimaryContainer.primaryBitstream;
newBitstream.name = rawForm.fileNamePrimaryContainer.fileName;
newBitstream.description = rawForm.descriptionContainer.description;
return newBitstream;
} }
/** /**

View File

@@ -15,7 +15,7 @@ import { Action, createSelector, MemoizedSelector, select, Store } from '@ngrx/s
import { ServerSyncBufferEntry, ServerSyncBufferState } from './server-sync-buffer.reducer'; import { ServerSyncBufferEntry, ServerSyncBufferState } from './server-sync-buffer.reducer';
import { combineLatest as observableCombineLatest, of as observableOf } from 'rxjs'; import { combineLatest as observableCombineLatest, of as observableOf } from 'rxjs';
import { RequestService } from '../data/request.service'; import { RequestService } from '../data/request.service';
import { PutRequest } from '../data/request.models'; import { PatchRequest, PutRequest } from '../data/request.models';
import { ObjectCacheService } from './object-cache.service'; import { ObjectCacheService } from './object-cache.service';
import { ApplyPatchObjectCacheAction } from './object-cache.actions'; import { ApplyPatchObjectCacheAction } from './object-cache.actions';
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
@@ -23,6 +23,8 @@ import { GenericConstructor } from '../shared/generic-constructor';
import { hasValue, isNotEmpty, isNotUndefined } from '../../shared/empty.util'; import { hasValue, isNotEmpty, isNotUndefined } from '../../shared/empty.util';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { RestRequestMethod } from '../data/rest-request-method'; import { RestRequestMethod } from '../data/rest-request-method';
import { ObjectCacheEntry } from './object-cache.reducer';
import { Operation } from 'fast-json-patch';
@Injectable() @Injectable()
export class ServerSyncBufferEffects { export class ServerSyncBufferEffects {
@@ -96,15 +98,16 @@ export class ServerSyncBufferEffects {
* @returns {Observable<Action>} ApplyPatchObjectCacheAction to be dispatched * @returns {Observable<Action>} ApplyPatchObjectCacheAction to be dispatched
*/ */
private applyPatch(href: string): Observable<Action> { private applyPatch(href: string): Observable<Action> {
const patchObject = this.objectCache.getObjectBySelfLink(href).pipe(take(1)); const patchObject = this.objectCache.getBySelfLink(href).pipe(take(1));
return patchObject.pipe( return patchObject.pipe(
map((object) => { map((entry: ObjectCacheEntry) => {
const serializedObject = new DSpaceRESTv2Serializer(object.constructor as GenericConstructor<{}>).serialize(object); if (isNotEmpty(entry.patches)) {
const flatPatch: Operation[] = [].concat(...entry.patches.map((patch) => patch.operations));
this.requestService.configure(new PutRequest(this.requestService.generateRequestId(), href, serializedObject)); this.requestService.configure(new PatchRequest(this.requestService.generateRequestId(), href, flatPatch));
return new ApplyPatchObjectCacheAction(href);
return new ApplyPatchObjectCacheAction(href) }
// this.requestService.configure(new PutRequest(this.requestService.generateRequestId(), href, serializedObject));
}) })
) )
} }

View File

@@ -22,6 +22,9 @@ export class DSOChangeAnalyzer<T extends DSpaceObject> implements ChangeAnalyzer
* The second object to compare * The second object to compare
*/ */
diff(object1: T | NormalizedDSpaceObject<T>, object2: T | NormalizedDSpaceObject<T>): Operation[] { diff(object1: T | NormalizedDSpaceObject<T>, object2: T | NormalizedDSpaceObject<T>): Operation[] {
return compare(object1.metadata, object2.metadata).map((operation: Operation) => Object.assign({}, operation, { path: '/metadata' + operation.path })); return compare(object1.metadata, object2.metadata)
// Filter out operations on UUIDs, as they should never change
.filter((operation: Operation) => !operation.path.endsWith('/uuid'))
.map((operation: Operation) => Object.assign({}, operation, { path: '/metadata' + operation.path }));
} }
} }

View File

@@ -94,7 +94,6 @@ export class ObjectUpdatesService {
const objectUpdates = this.getObjectEntry(url); const objectUpdates = this.getObjectEntry(url);
return objectUpdates.pipe(map((objectEntry) => { return objectUpdates.pipe(map((objectEntry) => {
const fieldUpdates: FieldUpdates = {}; const fieldUpdates: FieldUpdates = {};
console.log(objectEntry);
Object.keys(ignoreStates ? objectEntry.fieldUpdates : objectEntry.fieldStates).forEach((uuid) => { Object.keys(ignoreStates ? objectEntry.fieldUpdates : objectEntry.fieldStates).forEach((uuid) => {
let fieldUpdate = objectEntry.fieldUpdates[uuid]; let fieldUpdate = objectEntry.fieldUpdates[uuid];
if (isEmpty(fieldUpdate)) { if (isEmpty(fieldUpdate)) {

View File

@@ -4,19 +4,34 @@ import { Item } from './item.model';
import { BitstreamFormat } from './bitstream-format.model'; import { BitstreamFormat } from './bitstream-format.model';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { ResourceType } from './resource-type'; import { ResourceType } from './resource-type';
import { hasValue, isUndefined } from '../../shared/empty.util';
export class Bitstream extends DSpaceObject { export class Bitstream extends DSpaceObject {
static type = new ResourceType('bitstream'); static type = new ResourceType('bitstream');
private _description: string;
/** /**
* The size of this bitstream in bytes * The size of this bitstream in bytes
*/ */
sizeBytes: number; sizeBytes: number;
/** /**
* The description of this Bitstream * Get the description of this Bitstream
*/ */
description: string; get description(): string {
return (isUndefined(this._description)) ? this.firstMetadataValue('dc.description') : this._description;
}
/**
* Set the description of this Bitstream
*/
set description(description) {
if (hasValue(this.firstMetadata('dc.description'))) {
this.firstMetadata('dc.description').value = description;
}
this._description = description;
}
/** /**
* The name of the Bundle this Bitstream is part of * The name of the Bundle this Bitstream is part of

View File

@@ -7,7 +7,7 @@ import {
MetadatumViewModel MetadatumViewModel
} from './metadata.models'; } from './metadata.models';
import { Metadata } from './metadata.utils'; import { Metadata } from './metadata.utils';
import { hasNoValue, isUndefined } from '../../shared/empty.util'; import { hasNoValue, hasValue, isUndefined } from '../../shared/empty.util';
import { CacheableObject } from '../cache/object-cache.reducer'; import { CacheableObject } from '../cache/object-cache.reducer';
import { RemoteData } from '../data/remote-data'; import { RemoteData } from '../data/remote-data';
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model'; import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
@@ -47,6 +47,9 @@ export class DSpaceObject implements CacheableObject, ListableObject {
* The name for this DSpaceObject * The name for this DSpaceObject
*/ */
set name(name) { set name(name) {
if (hasValue(this.firstMetadata('dc.title'))) {
this.firstMetadata('dc.title').value = name;
}
this._name = name; this._name = name;
} }