From a10f0634253bf41a7728c5abac86d0ed892f525c Mon Sep 17 00:00:00 2001 From: Javier Martin Montull Date: Tue, 25 Jul 2017 15:19:28 +0200 Subject: [PATCH] WIP: handle out-of-date record --- .../editor-container.component.ts | 4 +- .../editor-toolbar-save.component.ts | 59 +++++++++++++++---- src/app/shared/services/api.service.ts | 23 ++++++-- 3 files changed, 68 insertions(+), 18 deletions(-) diff --git a/src/app/editor-container/editor-container.component.ts b/src/app/editor-container/editor-container.component.ts index 2fc0917..82981f1 100644 --- a/src/app/editor-container/editor-container.component.ts +++ b/src/app/editor-container/editor-container.component.ts @@ -44,8 +44,8 @@ export class EditorContainerComponent implements OnInit { this.route.params .subscribe(params => { this.apiService.fetchRecord(params['type'], params['recid']) - .then(record => { - this.record = record['metadata']; + .then(json => { + this.record = json['metadata']; this.config = this.appConfig.getConfigForRecord(this.record); return this.apiService.fetchUrl(this.record['$schema']); }).then(schema => { diff --git a/src/app/editor-toolbar/editor-toolbar-save/editor-toolbar-save.component.ts b/src/app/editor-toolbar/editor-toolbar-save/editor-toolbar-save.component.ts index f4d2b72..d57357e 100644 --- a/src/app/editor-toolbar/editor-toolbar-save/editor-toolbar-save.component.ts +++ b/src/app/editor-toolbar/editor-toolbar-save/editor-toolbar-save.component.ts @@ -53,6 +53,10 @@ export class EditorToolbarSaveComponent { onClickSave(event: Object) { this.recordCleanupService.cleanup(this.record); + this.previewAndSave(); + } + + previewAndSave() { this.http.post(`${environment.baseUrl}/editor/preview`, this.record) .subscribe((res: Response) => { this.modalService.displayModal({ @@ -60,17 +64,7 @@ export class EditorToolbarSaveComponent { bodyHtml: this.domSanitizer.bypassSecurityTrustHtml(''), type: 'confirm', onConfirm: () => { - this.apiService.saveRecord(this.record).subscribe({ - next: () => { - this.route.params - .subscribe(params => { - window.location.href = `/${params['type']}/${params['recid']}`; - }); - }, - error: (error) => { - console.warn(error.json()); - }, - }); + this.onPreviewConfirm() }, onShow: () => { let el = document.getElementById('iframe-preview') as HTMLIFrameElement; @@ -78,8 +72,49 @@ export class EditorToolbarSaveComponent { doc.open(); doc.write(res.text()); doc.close(); - } + } }); }); } + + onPreviewConfirm(record = undefined, revisionId = undefined) { + let _record = record || this.record; + this.apiService.saveRecord(_record, revisionId).subscribe({ + next: () => { + this.onSaveRecordSuccess(); + }, + error: (error: Response) => { + this.onSaveRecordError(error); + } + }); + } + + onSaveRecordSuccess() { + this.route.params + .subscribe(params => { + window.location.href = `/${params['type']}/${params['recid']}`; + }); + } + + onSaveRecordError(error) { + if (error.status === 412) { + // The version of the record on disk has changed + // Call API to merge versions + this.apiService.rebaseRecord(this.record).subscribe({ + next: (response: Response) => { + let json = response.json(); + if (json.conflicts) { + console.warn('Conflicts found while merging.', json.conflicts); + } else { + this.onPreviewConfirm(json.merged_json, json.revision_id); + } + }, + error: (error) => { + console.warn(error.json()); + } + }); + } else { + console.warn(error.json()); + } + } } diff --git a/src/app/shared/services/api.service.ts b/src/app/shared/services/api.service.ts index 7915229..210906f 100644 --- a/src/app/shared/services/api.service.ts +++ b/src/app/shared/services/api.service.ts @@ -21,7 +21,7 @@ */ import { Injectable } from '@angular/core'; -import { Http } from '@angular/http'; +import { Headers, Http, RequestOptions } from '@angular/http'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/toPromise'; @@ -32,6 +32,7 @@ export class ApiService { // pid_type and pid_value (see invenio-pidstore) private pidType: string; private pidValue: string; + private revisionId: string; // workflow object id (see invenio-workflows) private objectId: string; @@ -47,7 +48,11 @@ export class ApiService { fetchRecord(pidType: string, pidValue: string): Promise { this.pidType = pidType; this.pidValue = pidValue; - return this.fetchUrl(this.config.apiUrl(pidType, pidValue)); + return this.http.get(this.config.apiUrl(pidType, pidValue)) + .map(res => { + this.revisionId = res.headers.get('Etag'); + return res.json(); + }).toPromise(); } fetchWorkflowObject(objectId: string): Promise { @@ -55,12 +60,22 @@ export class ApiService { return this.fetchUrl(this.config.holdingPenApiUrl(this.objectId)); } - saveRecord(record: Object): Observable { + saveRecord(record: Object, revisionId = undefined): Observable { + let last_revision = revisionId || this.revisionId; + let headers = new Headers({ + 'If-Match': last_revision + }); + let options = new RequestOptions({ headers: headers }); return this.http - .put(this.config.apiUrl(this.pidType, this.pidValue), record) + .put(this.config.apiUrl(this.pidType, this.pidValue), record, options) .map(res => res.json()); } + rebaseRecord(record: Object): Observable { + return this.http + .post(`http://localhost:5000/api/editor/${this.pidType}/${this.pidValue}/rebase`, record); + } + saveWorkflowObject(record: Object): Observable { return this.http .put(this.config.holdingPenApiUrl(this.objectId), record)