From 1d3ada20f95067681cdfe407148c80ff92c3908d Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 13 Sep 2019 14:39:40 +0200 Subject: [PATCH] 64961: Edit bitstream metadata PATCH requests --- resources/i18n/en.json | 2 + .../edit-bitstream-page.component.ts | 44 +++++++++++++++++-- .../core/cache/server-sync-buffer.effects.ts | 19 ++++---- .../core/data/dso-change-analyzer.service.ts | 5 ++- .../object-updates/object-updates.service.ts | 1 - src/app/core/shared/bitstream.model.ts | 19 +++++++- src/app/core/shared/dspace-object.model.ts | 5 ++- 7 files changed, 78 insertions(+), 17 deletions(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index b951431d8f..2be34223d9 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -91,6 +91,8 @@ "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.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.dateissued": "By Issue Date", "browse.comcol.by.subject": "By Subject", diff --git a/src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts b/src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts index c74c0005d1..a1567a75a3 100644 --- a/src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts +++ b/src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; import { Bitstream } from '../../core/shared/bitstream.model'; import { ActivatedRoute } from '@angular/router'; -import { map } from 'rxjs/operators'; +import { map, tap } from 'rxjs/operators'; import { Subscription } from 'rxjs/internal/Subscription'; import { DynamicFormControlModel, DynamicFormGroupModel, DynamicFormLayout, DynamicFormService, @@ -11,6 +11,11 @@ import { import { FormGroup } from '@angular/forms'; import { TranslateService } from '@ngx-translate/core'; 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({ selector: 'ds-edit-bitstream-page', @@ -43,6 +48,11 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { */ 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 */ @@ -167,7 +177,9 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy { constructor(private route: ActivatedRoute, 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 */ onSubmit() { - // TODO: Check for changes against the bitstream and send requests to the REST API accordingly - console.log(this.formGroup.getRawValue()); + const updatedValues = this.formGroup.getRawValue(); + const newBitstream = this.formToBitstream(updatedValues); + this.bitstreamService.update(newBitstream).pipe( + tap(() => this.bitstreamService.commitUpdates()), + getSucceededRemoteData() + ).subscribe((bitstreamRD: RemoteData) => { + 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; } /** diff --git a/src/app/core/cache/server-sync-buffer.effects.ts b/src/app/core/cache/server-sync-buffer.effects.ts index 3aa6ad312f..19629ec7de 100644 --- a/src/app/core/cache/server-sync-buffer.effects.ts +++ b/src/app/core/cache/server-sync-buffer.effects.ts @@ -15,7 +15,7 @@ import { Action, createSelector, MemoizedSelector, select, Store } from '@ngrx/s import { ServerSyncBufferEntry, ServerSyncBufferState } from './server-sync-buffer.reducer'; import { combineLatest as observableCombineLatest, of as observableOf } from 'rxjs'; 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 { ApplyPatchObjectCacheAction } from './object-cache.actions'; 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 { Observable } from 'rxjs/internal/Observable'; import { RestRequestMethod } from '../data/rest-request-method'; +import { ObjectCacheEntry } from './object-cache.reducer'; +import { Operation } from 'fast-json-patch'; @Injectable() export class ServerSyncBufferEffects { @@ -96,15 +98,16 @@ export class ServerSyncBufferEffects { * @returns {Observable} ApplyPatchObjectCacheAction to be dispatched */ private applyPatch(href: string): Observable { - const patchObject = this.objectCache.getObjectBySelfLink(href).pipe(take(1)); + const patchObject = this.objectCache.getBySelfLink(href).pipe(take(1)); return patchObject.pipe( - map((object) => { - const serializedObject = new DSpaceRESTv2Serializer(object.constructor as GenericConstructor<{}>).serialize(object); - - this.requestService.configure(new PutRequest(this.requestService.generateRequestId(), href, serializedObject)); - - return new ApplyPatchObjectCacheAction(href) + map((entry: ObjectCacheEntry) => { + if (isNotEmpty(entry.patches)) { + const flatPatch: Operation[] = [].concat(...entry.patches.map((patch) => patch.operations)); + this.requestService.configure(new PatchRequest(this.requestService.generateRequestId(), href, flatPatch)); + return new ApplyPatchObjectCacheAction(href); + } + // this.requestService.configure(new PutRequest(this.requestService.generateRequestId(), href, serializedObject)); }) ) } diff --git a/src/app/core/data/dso-change-analyzer.service.ts b/src/app/core/data/dso-change-analyzer.service.ts index dd3487d3d0..205b696a24 100644 --- a/src/app/core/data/dso-change-analyzer.service.ts +++ b/src/app/core/data/dso-change-analyzer.service.ts @@ -22,6 +22,9 @@ export class DSOChangeAnalyzer implements ChangeAnalyzer * The second object to compare */ diff(object1: T | NormalizedDSpaceObject, object2: T | NormalizedDSpaceObject): 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 })); } } diff --git a/src/app/core/data/object-updates/object-updates.service.ts b/src/app/core/data/object-updates/object-updates.service.ts index c93dbe5c6b..fd439c98de 100644 --- a/src/app/core/data/object-updates/object-updates.service.ts +++ b/src/app/core/data/object-updates/object-updates.service.ts @@ -94,7 +94,6 @@ export class ObjectUpdatesService { const objectUpdates = this.getObjectEntry(url); return objectUpdates.pipe(map((objectEntry) => { const fieldUpdates: FieldUpdates = {}; - console.log(objectEntry); Object.keys(ignoreStates ? objectEntry.fieldUpdates : objectEntry.fieldStates).forEach((uuid) => { let fieldUpdate = objectEntry.fieldUpdates[uuid]; if (isEmpty(fieldUpdate)) { diff --git a/src/app/core/shared/bitstream.model.ts b/src/app/core/shared/bitstream.model.ts index 887f7d0843..bbd318c144 100644 --- a/src/app/core/shared/bitstream.model.ts +++ b/src/app/core/shared/bitstream.model.ts @@ -4,19 +4,34 @@ import { Item } from './item.model'; import { BitstreamFormat } from './bitstream-format.model'; import { Observable } from 'rxjs'; import { ResourceType } from './resource-type'; +import { hasValue, isUndefined } from '../../shared/empty.util'; export class Bitstream extends DSpaceObject { static type = new ResourceType('bitstream'); + private _description: string; + /** * The size of this bitstream in bytes */ 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 diff --git a/src/app/core/shared/dspace-object.model.ts b/src/app/core/shared/dspace-object.model.ts index 26f76c5ce2..9578ae6bc3 100644 --- a/src/app/core/shared/dspace-object.model.ts +++ b/src/app/core/shared/dspace-object.model.ts @@ -7,7 +7,7 @@ import { MetadatumViewModel } from './metadata.models'; 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 { RemoteData } from '../data/remote-data'; 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 */ set name(name) { + if (hasValue(this.firstMetadata('dc.title'))) { + this.firstMetadata('dc.title').value = name; + } this._name = name; }