From 44d3558e87583843b2d8e8961d2ad1f2aa500d0e Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 10 Sep 2021 18:16:35 +0200 Subject: [PATCH] [CST-4499] Version history (WIP) - 'Create version' button in item page and other fixes --- .../core/data/version-history-data.service.ts | 16 +-- .../publication/publication.component.html | 3 +- .../item-types/shared/item.component.ts | 51 ++++++++ .../untyped-item/untyped-item.component.html | 2 +- .../dso-page-version-button.component.html | 12 +- .../dso-page-version-button.component.ts | 25 ++-- .../item-versions-delete-modal.component.html | 6 +- .../item-versions-delete-modal.component.ts | 10 +- ...item-versions-summary-modal.component.html | 10 +- .../item-versions-summary-modal.component.ts | 9 +- .../item-versions.component.html | 4 +- .../item-versions/item-versions.component.ts | 116 +++++++++--------- src/assets/i18n/en.json5 | 8 +- 13 files changed, 161 insertions(+), 111 deletions(-) diff --git a/src/app/core/data/version-history-data.service.ts b/src/app/core/data/version-history-data.service.ts index 616cfdd48d..9bcedd6372 100644 --- a/src/app/core/data/version-history-data.service.ts +++ b/src/app/core/data/version-history-data.service.ts @@ -82,29 +82,17 @@ export class VersionHistoryDataService extends DataService { return this.versionDataService.findAllByHref(hrefObs, undefined, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); } - public createVersion(itemHref: string, summary: string): Observable> { + createVersion(itemHref: string, summary: string): Observable> { const requestOptions: HttpOptions = Object.create({}); let requestHeaders = new HttpHeaders(); requestHeaders = requestHeaders.append('Content-Type', 'text/uri-list'); requestOptions.headers = requestHeaders; - // TODO fix switchmap - return this.halService.getEndpoint(this.versionsEndpoint).pipe( take(1), - map((endpointUrl: string) => { - return (summary?.length > 0 ) ? `${endpointUrl}?summary=${summary}` : `${endpointUrl}`; - }), + map((endpointUrl: string) => (summary?.length > 0) ? `${endpointUrl}?summary=${summary}` : `${endpointUrl}`), map((endpointURL: string) => new PostRequest(this.requestService.generateRequestId(), endpointURL, itemHref, requestOptions)), sendRequest(this.requestService), - /*switchMap((res: string) => { - const requestId = this.requestService.generateRequestId(); - const href = res + ( (summary?.length > 0 ) ? ('?summary=' + summary) : ''); - const body = itemHref; - const request = new PostRequest(requestId, href, body, requestOptions); - this.requestService.send(request); - return this.rdbService.buildFromRequestUUID(requestId); - }),*/ switchMap((restRequest: RestRequest) => this.rdbService.buildFromRequestUUID(restRequest.uuid)), getFirstCompletedRemoteData() ) as Observable>; diff --git a/src/app/item-page/simple/item-types/publication/publication.component.html b/src/app/item-page/simple/item-types/publication/publication.component.html index 82eea0fafd..72824dd3e7 100644 --- a/src/app/item-page/simple/item-types/publication/publication.component.html +++ b/src/app/item-page/simple/item-types/publication/publication.component.html @@ -2,7 +2,8 @@

{{'publication.page.titleprefix' | translate}}

-
+
+
diff --git a/src/app/item-page/simple/item-types/shared/item.component.ts b/src/app/item-page/simple/item-types/shared/item.component.ts index 793af180c9..2b507aa921 100644 --- a/src/app/item-page/simple/item-types/shared/item.component.ts +++ b/src/app/item-page/simple/item-types/shared/item.component.ts @@ -2,6 +2,14 @@ import { Component, Input, OnInit } from '@angular/core'; import { environment } from '../../../../../environments/environment'; import { Item } from '../../../../core/shared/item.model'; import { getItemPageRoute } from '../../../item-page-routing-paths'; +import { ItemVersionsSummaryModalComponent } from '../../../../shared/item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component'; +import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; +import { take } from 'rxjs/operators'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { VersionHistoryDataService } from '../../../../core/data/version-history-data.service'; +import { NotificationsService } from '../../../../shared/notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { VersionDataService } from '../../../../core/data/version-data.service'; @Component({ selector: 'ds-item', @@ -20,6 +28,49 @@ export class ItemComponent implements OnInit { mediaViewer = environment.mediaViewer; + constructor( + private modalService: NgbModal, + private versionHistoryService: VersionHistoryDataService, + private notificationsService: NotificationsService, + private translateService: TranslateService, + // private itemService: ItemDataService, + private versionService: VersionDataService, + ) { + } + + createNewVersion() { + const successMessageKey = 'item.version.create.notification.success'; + const failureMessageKey = 'item.version.create.notification.failure'; + const item = this.object; + + // Open modal + const activeModal = this.modalService.open(ItemVersionsSummaryModalComponent); + + this.versionService.findByHref(item._links.version.href).pipe(getFirstCompletedRemoteData()).subscribe( + (res) => { + // TODO check serve async? + activeModal.componentInstance.firstVersion = res.hasNoContent; + activeModal.componentInstance.versionNumber = (res.hasNoContent ? undefined : res.payload.version); + } + ); + + // On modal submit/dismiss + activeModal.result.then((modalResult) => { + const summary = modalResult; + + const itemHref = item._links.self.href; + + this.versionHistoryService.createVersion(itemHref, summary).pipe(take(1)).subscribe((postResult) => { + if (postResult.hasSucceeded) { + const newVersionNumber = postResult.payload.version; + this.notificationsService.success(null, this.translateService.get(successMessageKey, {version: newVersionNumber})); + } else { + this.notificationsService.error(null, this.translateService.get(failureMessageKey)); + } + }); + }); + } + ngOnInit(): void { this.itemPageRoute = getItemPageRoute(this.object); } diff --git a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html index efe372ccaf..3ba7ddafd2 100644 --- a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html +++ b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html @@ -3,7 +3,7 @@
- +
diff --git a/src/app/shared/dso-page/dso-page-version-button/dso-page-version-button.component.html b/src/app/shared/dso-page/dso-page-version-button/dso-page-version-button.component.html index cf66a15503..1538d3bd89 100644 --- a/src/app/shared/dso-page/dso-page-version-button/dso-page-version-button.component.html +++ b/src/app/shared/dso-page/dso-page-version-button/dso-page-version-button.component.html @@ -1,7 +1,7 @@ - + diff --git a/src/app/shared/dso-page/dso-page-version-button/dso-page-version-button.component.ts b/src/app/shared/dso-page/dso-page-version-button/dso-page-version-button.component.ts index 72d9872965..7e12f4e1ff 100644 --- a/src/app/shared/dso-page/dso-page-version-button/dso-page-version-button.component.ts +++ b/src/app/shared/dso-page/dso-page-version-button/dso-page-version-button.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { Observable } from 'rxjs/internal/Observable'; @@ -18,11 +18,6 @@ export class DsoPageVersionButtonComponent implements OnInit { */ @Input() dso: DSpaceObject; - /** - * The prefix of the route to the edit page (before the object's UUID, e.g. "items") - */ - @Input() pageRoute: string; - /** * A message for the tooltip on the button * Supports i18n keys @@ -30,13 +25,27 @@ export class DsoPageVersionButtonComponent implements OnInit { @Input() tooltipMsg: string; /** - * Whether or not the current user is authorized to edit the DSpaceObject + * Emits an event that triggers the creation of the new version + */ + @Output() newVersionEvent = new EventEmitter(); + + /** + * Whether or not the current user is authorized to create a new version of the DSpaceObject */ isAuthorized$: Observable; - constructor(protected authorizationService: AuthorizationDataService) { } + constructor(protected authorizationService: AuthorizationDataService) { + } + + /** + * Creates a new version for the current item + */ + createNewVersion() { + this.newVersionEvent.emit(); + } ngOnInit() { + // TODO show if user can view history this.isAuthorized$ = this.authorizationService.isAuthorized(FeatureID.CanEditMetadata, this.dso.self); } diff --git a/src/app/shared/item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component.html b/src/app/shared/item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component.html index e133c3b61f..0c0b72272f 100644 --- a/src/app/shared/item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component.html +++ b/src/app/shared/item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component.html @@ -8,17 +8,15 @@

{{ "item.version.delete.modal.text" | translate : {version: versionNumber} }}

diff --git a/src/app/shared/item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component.ts b/src/app/shared/item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component.ts index 6bdfd10939..35618390d9 100644 --- a/src/app/shared/item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component.ts +++ b/src/app/shared/item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; @Component({ @@ -6,12 +6,13 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; templateUrl: './item-versions-delete-modal.component.html', styleUrls: ['./item-versions-delete-modal.component.scss'] }) -export class ItemVersionsDeleteModalComponent implements OnInit { +export class ItemVersionsDeleteModalComponent { versionNumber: number; constructor( - protected activeModal: NgbActiveModal,) { } + protected activeModal: NgbActiveModal,) { + } onModalClose() { this.activeModal.dismiss(); @@ -21,7 +22,4 @@ export class ItemVersionsDeleteModalComponent implements OnInit { this.activeModal.close(); } - ngOnInit(): void { - } - } diff --git a/src/app/shared/item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component.html b/src/app/shared/item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component.html index 94b4d4dc51..6065cb289e 100644 --- a/src/app/shared/item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component.html +++ b/src/app/shared/item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component.html @@ -5,8 +5,10 @@ diff --git a/src/app/shared/item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component.ts b/src/app/shared/item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component.ts index 236f058c63..30dd71ca20 100644 --- a/src/app/shared/item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component.ts +++ b/src/app/shared/item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; @Component({ @@ -6,10 +6,11 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; templateUrl: './item-versions-summary-modal.component.html', styleUrls: ['./item-versions-summary-modal.component.scss'] }) -export class ItemVersionsSummaryModalComponent implements OnInit { +export class ItemVersionsSummaryModalComponent { versionNumber: number; newVersionSummary: string; + firstVersion: boolean; constructor( protected activeModal: NgbActiveModal, @@ -24,8 +25,4 @@ export class ItemVersionsSummaryModalComponent implements OnInit { this.activeModal.close(this.newVersionSummary); } - ngOnInit(): void { - // TODO delete if unused - } - } diff --git a/src/app/shared/item/item-versions/item-versions.component.html b/src/app/shared/item/item-versions/item-versions.component.html index 6c4724d045..4e488a3e23 100644 --- a/src/app/shared/item/item-versions/item-versions.component.html +++ b/src/app/shared/item/item-versions/item-versions.component.html @@ -44,7 +44,7 @@ @@ -72,7 +72,7 @@ diff --git a/src/app/shared/item/item-versions/item-versions.component.ts b/src/app/shared/item/item-versions/item-versions.component.ts index cfeb84151b..d68c201359 100644 --- a/src/app/shared/item/item-versions/item-versions.component.ts +++ b/src/app/shared/item/item-versions/item-versions.component.ts @@ -7,6 +7,7 @@ import { VersionHistory } from '../../../core/shared/version-history.model'; import { getAllSucceededRemoteData, getAllSucceededRemoteDataPayload, + getFirstCompletedRemoteData, getFirstSucceededRemoteData, getFirstSucceededRemoteDataPayload, getRemoteDataPayload @@ -29,19 +30,18 @@ import { TranslateService } from '@ngx-translate/core'; import { ItemVersionsDeleteModalComponent } from './item-versions-delete-modal/item-versions-delete-modal.component'; import { VersionDataService } from '../../../core/data/version-data.service'; import { ItemDataService } from '../../../core/data/item-data.service'; -import { ObjectCacheService } from '../../../core/cache/object-cache.service'; @Component({ selector: 'ds-item-versions', templateUrl: './item-versions.component.html', styleUrls: ['./item-versions.component.scss'] }) + /** * Component listing all available versions of the history the provided item is a part of */ export class ItemVersionsComponent implements OnInit { - /** * The item to display a version history for */ @@ -60,10 +60,13 @@ export class ItemVersionsComponent implements OnInit { @Input() displayTitle = true; /** - * Whether or not to display the action buttons + * Whether or not to display the action buttons (delete/create/edit version) */ @Input() displayActions: boolean; + /** + * Array of active subscriptions + */ subs: Subscription[] = []; /** @@ -123,7 +126,8 @@ export class ItemVersionsComponent implements OnInit { }>; /** - * Emits when the versionsRD$ must be refreshed. + * Emits when the versionsRD$ must be refreshed + * (should be used when a new version has been created) */ refreshSubject = new BehaviorSubject(null); @@ -137,23 +141,11 @@ export class ItemVersionsComponent implements OnInit { */ versionBeingEditedId: string; -// itemLink: string; TODO delete - /** * The summary currently being edited */ versionBeingEditedSummary: string; - - /** - * Cancel the current edit when component is destroyed & unsub all subscriptions - */ - // @HostListener('document:keydown:enter') - // onSummarySubmitKeydownEvent(event: KeyboardEvent): void { - // event.preventDefault(); - // } - - constructor(private versionHistoryService: VersionHistoryDataService, private versionService: VersionDataService, private itemService: ItemDataService, @@ -162,40 +154,52 @@ export class ItemVersionsComponent implements OnInit { private modalService: NgbModal, private notificationsService: NotificationsService, private translateService: TranslateService, - private cacheService: ObjectCacheService, + // private cacheService: ObjectCacheService, ) { } - + /** + * True when a version is being edited + * (used to disable buttons for other versions) + */ isAnyBeingEdited(): boolean { return this.versionBeingEditedNumber != null; } + /** + * True if the specified version is being edited + * (used to show input field and to change buttons for specified version) + */ isThisBeingEdited(version): boolean { return version?.version === this.versionBeingEditedNumber; } - editVersionSummary(version): void { + /** + * Enables editing for the specified version + */ + enableVersionEditing(version): void { this.versionBeingEditedSummary = version?.summary; this.versionBeingEditedNumber = version?.version; this.versionBeingEditedId = version?.id; } - discardSummaryEdits(): void { + /** + * Disables editing for the specified version and discards all pending changes + */ + disableSummaryEditing(): void { this.versionBeingEditedSummary = undefined; this.versionBeingEditedNumber = undefined; this.versionBeingEditedId = undefined; } + /** + * Applies changes to version currently being edited + */ onSummarySubmit() { const successMessageKey = 'item.version.edit.notification.success'; const failureMessageKey = 'item.version.edit.notification.failure'; - const newSummary = this.versionBeingEditedSummary ?? ''; - - // TODO submit - this.versionService.findById(this.versionBeingEditedId).pipe(getFirstSucceededRemoteData()).subscribe( (findRes) => { const updatedVersion = @@ -210,16 +214,17 @@ export class ItemVersionsComponent implements OnInit { } else { this.notificationsService.warning(null, this.translateService.get(failureMessageKey, {'version': this.versionBeingEditedNumber})); } + this.disableSummaryEditing(); } ); } ); - - console.log('SUBMITTING ' + this.versionBeingEditedSummary); - this.versionBeingEditedNumber = undefined; } - + /** + * Deletes the specified version + * @param version the version to be deleted + */ deleteVersion(version) { const successMessageKey = 'item.version.delete.notification.success'; const failureMessageKey = 'item.version.delete.notification.failure'; @@ -228,14 +233,29 @@ export class ItemVersionsComponent implements OnInit { // Open modal const activeModal = this.modalService.open(ItemVersionsDeleteModalComponent); activeModal.componentInstance.versionNumber = version.version; + activeModal.componentInstance.firstVersion = false; // On modal submit/dismiss - activeModal.result.then((modalResult) => { - // TODO delete version - // TODO non usare due subscriptions innestate ma uno switchmap + activeModal.result.then(() => { version.item.pipe(getFirstSucceededRemoteDataPayload()).subscribe((getItemRes) => { - this.itemService.delete(getItemRes.id).pipe(take(1)).subscribe( // TODO provare con getfirstcompletedremotedata + this.itemService.delete(getItemRes.id).pipe(getFirstCompletedRemoteData()).subscribe( (deleteItemRes) => { + console.log(JSON.stringify(deleteItemRes)); + if (deleteItemRes.hasSucceeded) { + this.notificationsService.success(null, this.translateService.get(successMessageKey, {'version': versionNumber})); + this.refreshSubject.next(null); + } else { + this.notificationsService.error(null, this.translateService.get(failureMessageKey, {'version': versionNumber})); + } + } + ); + }); + /*version.item.pipe( + getFirstSucceededRemoteDataPayload(), + switchMap((getItemRes) => this.itemService.delete(getItemRes.id)) + ).subscribe( + getFirstCompletedRemoteData(), + map((deleteItemRes) => { console.log(JSON.stringify(deleteItemRes)); if (deleteItemRes.hasSucceeded) { this.notificationsService.success(null, this.translateService.get(successMessageKey, {'version': versionNumber})); @@ -243,17 +263,15 @@ export class ItemVersionsComponent implements OnInit { this.notificationsService.warning(null, this.translateService.get(failureMessageKey, {'version': versionNumber})); } } - ); - }); - }).catch(() => { - this.notificationsService.warning(null, this.translateService.get(failureMessageKey, {'version': versionNumber})); - } - ); + ) + );*/ + }); } - - // TODO aggiungere create anche alla pagina dell'item (spostare in file esterno?) - + /** + * Creates a new version starting from the specified one + * @param version the version from which a new one will be created + */ createNewVersion(version) { const successMessageKey = 'item.version.create.notification.success'; const failureMessageKey = 'item.version.create.notification.failure'; @@ -280,14 +298,9 @@ export class ItemVersionsComponent implements OnInit { } }); }); - }).catch(() => { - this.notificationsService.warning(null, this.translateService.get(failureMessageKey)); - } - ); - + }); } - /** * Initialize all observables */ @@ -313,7 +326,7 @@ export class ItemVersionsComponent implements OnInit { true, true, followLink('item'), followLink('eperson')); }) ); - // TODO comment refresh + // Refresh the table when refreshSubject emits this.subs.push(this.refreshSubject.pipe(switchMap(() => { return observableCombineLatest([versionHistory$, currentPagination]).pipe( take(1), @@ -324,13 +337,6 @@ export class ItemVersionsComponent implements OnInit { }) ); })).subscribe()); - - - /* TODO fix error and restore refresh - The response for 'http://localhost:8080/server/api/versioning/versionhistories/1/versions?page=0&size=1' - has the self link 'http://localhost:8080/server/api/versioning/versionhistories/1/versions?page=0&embed=item&size=1'. - These don't match. This could mean there's an issue with the REST endpoint - */ this.hasEpersons$ = this.versionsRD$.pipe( getAllSucceededRemoteData(), getRemoteDataPayload(), diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 4496c0dab5..16dbd7dba7 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1996,9 +1996,11 @@ "item.version.notice": "This is not the latest version of this item. The latest version can be found here.", - "item.version.create.modal.header": "Create new version", + "item.version.create.modal.header": "New version", - "item.version.create.modal.text": "Create a new version for this item starting from version {{version}}", + "item.version.create.modal.text": "Create a new version for this item", + + "item.version.create.modal.text.startingFrom": "starting from version {{version}}", "item.version.create.modal.button.confirm": "Create", @@ -2017,7 +2019,7 @@ "item.version.create.notification.failure" : "New version has not been created", - "item.version.delete.modal.header": "Delete version version", + "item.version.delete.modal.header": "Delete version", "item.version.delete.modal.text": "Do you want to delete version {{version}}?",