[CST-4499] Version history (WIP) - 'Create version' button in item page and other fixes

This commit is contained in:
Davide Negretti
2021-09-10 18:16:35 +02:00
parent ad5ace79fb
commit 44d3558e87
13 changed files with 161 additions and 111 deletions

View File

@@ -82,29 +82,17 @@ export class VersionHistoryDataService extends DataService<VersionHistory> {
return this.versionDataService.findAllByHref(hrefObs, undefined, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
}
public createVersion(itemHref: string, summary: string): Observable<RemoteData<Version>> {
createVersion(itemHref: string, summary: string): Observable<RemoteData<Version>> {
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<Version>(requestId);
}),*/
switchMap((restRequest: RestRequest) => this.rdbService.buildFromRequestUUID(restRequest.uuid)),
getFirstCompletedRemoteData()
) as Observable<RemoteData<Version>>;

View File

@@ -2,7 +2,8 @@
<h2 class="item-page-title-field mr-auto">
{{'publication.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
</h2>
<div class="pl-2" style="border: solid 2px red;">
<div class="pl-2">
<ds-dso-page-version-button (newVersionEvent)="createNewVersion()" [dso]="object" [tooltipMsg]="'item.page.version'"></ds-dso-page-version-button>
<ds-dso-page-edit-button [pageRoute]="itemPageRoute" [dso]="object" [tooltipMsg]="'publication.page.edit'"></ds-dso-page-edit-button>
</div>
</div>

View File

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

View File

@@ -3,7 +3,7 @@
<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
</h2>
<div class="pl-2">
<ds-dso-page-version-button [pageRoute]="itemPageRoute" [dso]="object" [tooltipMsg]="'item.page.version'"></ds-dso-page-version-button>
<ds-dso-page-version-button (newVersionEvent)="createNewVersion()" [dso]="object" [tooltipMsg]="'item.page.version'"></ds-dso-page-version-button>
<ds-dso-page-edit-button [pageRoute]="itemPageRoute" [dso]="object" [tooltipMsg]="'item.page.edit'"></ds-dso-page-edit-button>
</div>
</div>

View File

@@ -1,7 +1,7 @@
<a *ngIf="isAuthorized$ | async"
[routerLink]="[pageRoute, 'edit', 'versionhistory', 'create']"
<button *ngIf="isAuthorized$ | async"
class="edit-button btn btn-dark btn-sm"
(click)="createNewVersion()"
[ngbTooltip]="tooltipMsg | translate"
role="button" [title]="tooltipMsg |translate" [attr.aria-label]="tooltipMsg |translate">
<i class="fas fa-code-branch fa-fw"></i>
</a>
</button>

View File

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

View File

@@ -8,17 +8,15 @@
<p class="pb-2">{{ "item.version.delete.modal.text" | translate : {version: versionNumber} }}</p>
</div>
<div class="modal-footer">
<!-- <div class="btn-group">-->
<button class="btn btn-danger btn-sm"
<button class="btn btn-outline-secondary btn-sm"
(click)="onModalClose()"
title="{{'item.version.delete.modal.button.cancel.tooltip' | translate}}">
<i class="fas fa-times fa-fw"></i> {{'item.version.delete.modal.button.cancel' | translate}}
</button>
<button class="btn btn-success btn-sm"
<button class="btn btn-danger btn-sm"
(click)="onModalSubmit()"
title="{{'item.version.delete.modal.button.confirm.tooltip' | translate}}">
<i class="fas fa-check fa-fw"></i> {{'item.version.delete.modal.button.confirm' | translate}}
</button>
<!-- </div>-->
</div>
</div>

View File

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

View File

@@ -5,8 +5,10 @@
</button>
</div>
<div class="modal-body">
<!--<h5 *ngIf="header" class="px-2">{{header | translate}}</h5>-->
<p class="pb-2">{{ "item.version.create.modal.text" | translate : {version: versionNumber} }}</p>
<p class="pb-2">
{{ "item.version.create.modal.text" | translate }}
<span *ngIf="!firstVersion">{{ "item.version.create.modal.text.startingFrom" | translate : {version: versionNumber} }}</span>
</p>
<div class="form-group">
<label for="summary">{{'item.version.create.modal.form.summary.label' | translate }}:</label>
<input type="text" id="summary" required class="form-control" [(ngModel)]="newVersionSummary"
@@ -14,8 +16,7 @@
</div>
</div>
<div class="modal-footer">
<!-- <div class="btn-group">-->
<button class="btn btn-danger btn-sm"
<button class="btn btn-outline-secondary btn-sm"
(click)="onModalClose()"
title="{{'item.version.create.modal.button.cancel.tooltip' | translate}}">
<i class="fas fa-times fa-fw"></i> {{'item.version.create.modal.button.cancel' | translate}}
@@ -25,6 +26,5 @@
title="{{'item.version.create.modal.button.confirm.tooltip' | translate}}">
<i class="fas fa-check fa-fw"></i> {{'item.version.create.modal.button.confirm' | translate}}
</button>
<!-- </div>-->
</div>
</div>

View File

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

View File

@@ -44,7 +44,7 @@
<button class="btn btn-outline-primary btn-sm"
*ngIf="!isThisBeingEdited(version)"
[disabled]="isAnyBeingEdited()"
(click)="editVersionSummary(version)"
(click)="enableVersionEditing(version)"
title="{{'item.version.history.table.action.editSummary' | translate}}">
<i class="fas fa-edit fa-fw"></i>
</button>
@@ -72,7 +72,7 @@
<button class="btn btn-sm"
[ngClass]="isThisBeingEdited(version) ? 'btn-outline-warning' : 'btn-outline-primary'"
[disabled]="!isAnyBeingEdited() || !isThisBeingEdited(version)"
(click)="discardSummaryEdits()"
(click)="disableSummaryEditing()"
title="{{'item.version.history.table.action.discardSummary' | translate}}">
<i class="fas fa-undo-alt fa-fw"></i>
</button>

View File

@@ -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<any>(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,13 +298,8 @@ 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(),

View File

@@ -1996,9 +1996,11 @@
"item.version.notice": "This is not the latest version of this item. The latest version can be found <a href='{{destination}}'>here</a>.",
"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}}?",