diff --git a/src/app/core/data/feature-authorization/feature-id.ts b/src/app/core/data/feature-authorization/feature-id.ts index ac045b93b0..4716fbf413 100644 --- a/src/app/core/data/feature-authorization/feature-id.ts +++ b/src/app/core/data/feature-authorization/feature-id.ts @@ -21,4 +21,7 @@ export enum FeatureID { CanManagePolicies = 'canManagePolicies', CanMakePrivate = 'canMakePrivate', CanMove = 'canMove', + CanEditVersion = 'canEditVersion', + CanDeleteVersion = 'canDeleteVersion', + CanCreateVersion = 'canCreateVersion', } diff --git a/src/app/core/data/version-history-data.service.ts b/src/app/core/data/version-history-data.service.ts index 783b2bae94..0064292114 100644 --- a/src/app/core/data/version-history-data.service.ts +++ b/src/app/core/data/version-history-data.service.ts @@ -16,7 +16,7 @@ import { PaginatedSearchOptions } from '../../shared/search/paginated-search-opt import { RemoteData } from './remote-data'; import { PaginatedList } from './paginated-list.model'; import { Version } from '../shared/version.model'; -import { filter, map, switchMap, take } from 'rxjs/operators'; +import { filter, map, startWith, switchMap, take } from 'rxjs/operators'; import { dataService } from '../cache/builders/build-decorators'; import { VERSION_HISTORY } from '../shared/version-history.resource-type'; import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; @@ -127,16 +127,30 @@ export class VersionHistoryDataService extends DataService { } + // TODO move to versionDataService + getHistoryIdFromVersion$(version: Version): Observable { + return this.getHistoryFromVersion$(version).pipe( + map((versionHistory) => versionHistory.id), + ); + } + + // TODO move to versionDataService + getHistoryFromVersion$(version: Version): Observable { + return this.versionDataService.findById(version.id, false, true, followLink('versionhistory')).pipe( + getFirstSucceededRemoteDataPayload(), + switchMap((res) => res.versionhistory), + getFirstSucceededRemoteDataPayload(), + ); + } + +// TODO move to versionDataService getLatestVersion$(version: Version): Observable { // retrieve again version, including with versionHistory return this.versionDataService.findById(version.id, false, true, followLink('versionhistory')).pipe( getFirstSucceededRemoteDataPayload(), switchMap((res) => res.versionhistory), getFirstSucceededRemoteDataPayload(), - switchMap((versionHistoryRD) => { - return this.getLatestVersionFromHistory$(versionHistoryRD); - } - ), + switchMap((versionHistoryRD) => this.getLatestVersionFromHistory$(versionHistoryRD)), ); } @@ -147,4 +161,16 @@ export class VersionHistoryDataService extends DataService { ); } + /** + * Check if a worskpace item exists in the version history + * @param versionHref the href of the version + */ + hasDraftVersion$(versionHref: string): Observable { + return this.versionDataService.findByHref(versionHref, true, true, followLink('versionhistory')).pipe( + getFirstSucceededRemoteDataPayload(), + switchMap((version) => this.getHistoryFromVersion$(version)), + map((versionHistory) => versionHistory.draftVersion), + startWith(false), + ); + } } diff --git a/src/app/core/shared/version-history.model.ts b/src/app/core/shared/version-history.model.ts index 1448317427..6f9423c5fe 100644 --- a/src/app/core/shared/version-history.model.ts +++ b/src/app/core/shared/version-history.model.ts @@ -9,6 +9,7 @@ import { link, typedObject } from '../cache/builders/build-decorators'; import { DSpaceObject } from './dspace-object.model'; import { HALLink } from './hal-link.model'; import { VERSION } from './version.resource-type'; +import { ResourceType } from './resource-type'; /** * Class representing a DSpace Version History @@ -22,6 +23,7 @@ export class VersionHistory extends DSpaceObject { _links: { self: HALLink; versions: HALLink; + draftVersion: HALLink; }; /** @@ -42,6 +44,12 @@ export class VersionHistory extends DSpaceObject { @autoserialize submitterName: string; + /** + * Whether exist a workspace item + */ + @autoserialize + draftVersion: boolean; + /** * The list of versions within this history */ 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 72824dd3e7..9e61f00e48 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 @@ -3,7 +3,6 @@ {{'publication.page.titleprefix' | translate}}
-
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 3ba7ddafd2..a370f2b53b 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,9 @@
- +
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 1538d3bd89..a46b48244a 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,8 @@ 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 7e12f4e1ff..80d39a49a7 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,8 +1,12 @@ 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'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; +import { VersionHistoryDataService } from '../../../core/data/version-history-data.service'; +import { Item } from '../../../core/shared/item.model'; +import { switchMap } from 'rxjs/operators'; +import { VersionDataService } from '../../../core/data/version-data.service'; +import { of } from 'rxjs'; @Component({ selector: 'ds-dso-page-version-button', @@ -14,15 +18,21 @@ import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; */ export class DsoPageVersionButtonComponent implements OnInit { /** - * The DSpaceObject to display a button to the edit page for + * The item for which display a button to create a new version */ - @Input() dso: DSpaceObject; + @Input() dso: Item; /** * A message for the tooltip on the button * Supports i18n keys */ - @Input() tooltipMsg: string; + @Input() tooltipMsgCreate: string; + + /** + * A message for the tooltip on the button (when is disabled) + * Supports i18n keys + */ + @Input() tooltipMsgHasDraft: string; /** * Emits an event that triggers the creation of the new version @@ -34,7 +44,14 @@ export class DsoPageVersionButtonComponent implements OnInit { */ isAuthorized$: Observable; - constructor(protected authorizationService: AuthorizationDataService) { + hasDraftVersion$: Observable; + + tooltipMsg$: Observable; + + constructor( + protected authorizationService: AuthorizationDataService, + protected versionHistoryService: VersionHistoryDataService, + ) { } /** @@ -45,8 +62,11 @@ export class DsoPageVersionButtonComponent implements OnInit { } ngOnInit() { - // TODO show if user can view history - this.isAuthorized$ = this.authorizationService.isAuthorized(FeatureID.CanEditMetadata, this.dso.self); + this.isAuthorized$ = this.authorizationService.isAuthorized(FeatureID.CanCreateVersion, this.dso.self); + this.hasDraftVersion$ = this.versionHistoryService.hasDraftVersion$(this.dso._links.version.href); + this.tooltipMsg$ = this.hasDraftVersion$.pipe( + switchMap((hasDraftVersion) => of(hasDraftVersion ? this.tooltipMsgHasDraft : this.tooltipMsgCreate)), + ); } } 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 500ef602b9..f39f2b8b70 100644 --- a/src/app/shared/item/item-versions/item-versions.component.html +++ b/src/app/shared/item/item-versions/item-versions.component.html @@ -46,41 +46,50 @@
- - + + + + - + + + - - + + + + + + +
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 afde91b8ff..cdd4bc79f9 100644 --- a/src/app/shared/item/item-versions/item-versions.component.ts +++ b/src/app/shared/item/item-versions/item-versions.component.ts @@ -2,7 +2,14 @@ import { Component, Input, OnInit } from '@angular/core'; import { Item } from '../../../core/shared/item.model'; import { Version } from '../../../core/shared/version.model'; import { RemoteData } from '../../../core/data/remote-data'; -import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs'; +import { + BehaviorSubject, + combineLatest, + combineLatest as observableCombineLatest, + Observable, + of, + Subscription +} from 'rxjs'; import { VersionHistory } from '../../../core/shared/version-history.model'; import { getAllSucceededRemoteData, @@ -12,7 +19,7 @@ import { getFirstSucceededRemoteDataPayload, getRemoteDataPayload } from '../../../core/shared/operators'; -import { map, startWith, switchMap, take } from 'rxjs/operators'; +import { map, mergeMap, startWith, switchMap, take } from 'rxjs/operators'; import { PaginatedList } from '../../../core/data/paginated-list.model'; import { PaginationComponentOptions } from '../../pagination/pagination-component-options.model'; import { VersionHistoryDataService } from '../../../core/data/version-history-data.service'; @@ -35,6 +42,8 @@ import { ItemVersionsDeleteModalComponent } from './item-versions-delete-modal/i import { VersionDataService } from '../../../core/data/version-data.service'; import { ItemDataService } from '../../../core/data/item-data.service'; import { Router } from '@angular/router'; +import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; +import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; @Component({ selector: 'ds-item-versions', @@ -101,6 +110,12 @@ export class ItemVersionsComponent implements OnInit { */ hasEpersons$: Observable; + /** + * Verify if there is an inprogress submission in the version history + * Used to disable the "Create version" button + */ + hasDraftVersion$: Observable; + /** * The amount of versions to display per page */ @@ -151,6 +166,9 @@ export class ItemVersionsComponent implements OnInit { */ versionBeingEditedSummary: string; + canCreateVersion$: Observable; + createVersionTitle$: Observable; + constructor(private versionHistoryService: VersionHistoryDataService, private versionService: VersionDataService, private itemService: ItemDataService, @@ -160,6 +178,7 @@ export class ItemVersionsComponent implements OnInit { private notificationsService: NotificationsService, private translateService: TranslateService, private router: Router, + protected authorizationService: AuthorizationDataService, ) { } @@ -250,9 +269,113 @@ export class ItemVersionsComponent implements OnInit { activeModal.componentInstance.versionNumber = version.version; activeModal.componentInstance.firstVersion = false; + + // OLD + const versionHistory$ = this.versionHistoryService.getHistoryIdFromVersion$(version).pipe( + take(1), + switchMap((res) => this.versionHistoryService.findById(res)), + getFirstSucceededRemoteDataPayload(), + ); + // On modal submit/dismiss activeModal.result.then(() => { - console.log('Deleting item...'); + + /*const versionHistory$ = this.versionHistoryService.getHistoryIdFromVersion$(version).pipe( + take(1), + map((versionHistoryId) => { + console.log('Version history ID = ' + versionHistoryId); + return versionHistoryId; + }), + switchMap((res) => this.versionHistoryService.findById(res)), + getFirstSucceededRemoteDataPayload(), + );*/ + + /*const deleteResultWithVersionHistory$ = versionHistory$.pipe( + switchMap((versionHisory) => combineLatest([ + versionItem$.pipe( + getFirstSucceededRemoteDataPayload(), + switchMap((itemBeingDeleted) => this.itemService.delete(itemBeingDeleted.id)), + getFirstCompletedRemoteData(), + map((deleteItemRes) => deleteItemRes.hasSucceeded), + ), + of(versionHisory) + ]) + ) + );*/ + + /*const deleteResultWithNewLatestVersion = deleteResultWithVersionHistory$.pipe( + switchMap( ([deleteHasSucceeded, versionHisory]) => [ + of(deleteHasSucceeded), + of(versionHisory).pipe( + switchMap((vh) => this.versionHistoryService.getLatestVersionFromHistory$(vh)), + switchMap((newLatestVersion) => newLatestVersion.item), + getFirstSucceededRemoteDataPayload(), + ) + ]), + ).subscribe(([deleteHasSucceeded, newLatestVersionItem] ) => { + if (deleteHasSucceeded) { + this.notificationsService.success(null, this.translateService.get(successMessageKey, {'version': versionNumber})); + } else { + this.notificationsService.error(null, this.translateService.get(failureMessageKey, {'version': versionNumber})); + } + console.log('LATEST VERSION = ' + newLatestVersionItem.uuid); + if (redirectToLatest) { + const tmpPath = getItemEditVersionhistoryRoute(newLatestVersionItem); + console.log('PATH = ' + tmpPath); + this.router.navigateByUrl(tmpPath); + } + return this.versionHistoryService.getLatestVersion$(version); + });*/ + + /* + * 1. recuperare version history + * 2. successivamente eliminare l'item + * 3. dopo aver eliminato l'item, recuperare la nuova versione + * 4. fare il redirect + */ + + /*versionItem$.pipe( + getFirstSucceededRemoteDataPayload(), + map((item) => item.id), + mergeMap((itemId) => combineLatest([ + of(itemId).pipe( + // BEGIN DELETE + switchMap((id) => this.itemService.delete(id)), + getFirstCompletedRemoteData(), + map((deleteItemRes) => deleteItemRes.hasSucceeded), + // END DELETE + ), + + this.versionHistoryService.getHistoryIdFromVersion$(version).pipe( + take(1), + map((versionHistoryId) => { + console.log('Version history ID = ' + versionHistoryId); + return versionHistoryId; + }), + switchMap((res) => this.versionHistoryService.findById(res)), + getFirstSucceededRemoteDataPayload(), + switchMap((vh) => this.versionHistoryService.getLatestVersionFromHistory$(vh)), + switchMap((newLatestVersion) => newLatestVersion.item), + getFirstSucceededRemoteDataPayload(), + mergeMap((deleteHasSucceeded) => combineLatest([])) + ) + + ]) + ), + ).subscribe(([deleteHasSucceeded, newLatestVersionItem]) => { + if (deleteHasSucceeded) { + this.notificationsService.success(null, this.translateService.get(successMessageKey, {'version': versionNumber})); + } else { + this.notificationsService.error(null, this.translateService.get(failureMessageKey, {'version': versionNumber})); + } + console.log('LATEST VERSION = ' + newLatestVersionItem.uuid); + if (redirectToLatest) { + const tmpPath = getItemEditVersionhistoryRoute(newLatestVersionItem); + console.log('PATH = ' + tmpPath); + this.router.navigateByUrl(tmpPath); + } + return this.versionHistoryService.getLatestVersion$(version); + });*/ versionItem$.pipe( getFirstSucceededRemoteDataPayload(), @@ -260,82 +383,30 @@ export class ItemVersionsComponent implements OnInit { switchMap((itemId) => this.itemService.delete(itemId)), getFirstCompletedRemoteData(), map((deleteItemRes) => deleteItemRes.hasSucceeded), - switchMap((deleteHasSucceeded) => { - if (deleteHasSucceeded) { - this.notificationsService.success(null, this.translateService.get(successMessageKey, {'version': versionNumber})); - } else { - this.notificationsService.error(null, this.translateService.get(failureMessageKey, {'version': versionNumber})); - } - return this.versionHistoryService.getLatestVersion$(version); - }), - switchMap((latestVersion) => latestVersion.item), - getFirstSucceededRemoteDataPayload(), - ).subscribe((latestVersionItem) => { - console.log('LATEST VERSION = ' + latestVersionItem.uuid); + mergeMap((deleteHasSucceeded) => combineLatest([ + of(deleteHasSucceeded), + versionHistory$.pipe( + switchMap((vh) => this.versionHistoryService.getLatestVersionFromHistory$(vh)), + switchMap((newLatestVersion) => newLatestVersion.item), + getFirstSucceededRemoteDataPayload() + ) + ]) + ), + ).subscribe(([deleteHasSucceeded, newLatestVersionItem]) => { + if (deleteHasSucceeded) { + this.notificationsService.success(null, this.translateService.get(successMessageKey, {'version': versionNumber})); + } else { + this.notificationsService.error(null, this.translateService.get(failureMessageKey, {'version': versionNumber})); + } + console.log('LATEST VERSION = ' + newLatestVersionItem.uuid); if (redirectToLatest) { - const tmpPath = getItemEditVersionhistoryRoute(latestVersionItem); + const tmpPath = getItemEditVersionhistoryRoute(newLatestVersionItem); console.log('PATH = ' + tmpPath); this.router.navigateByUrl(tmpPath); } + return this.versionHistoryService.getLatestVersion$(version); }); - - /* - const itemVersion$ = item.version; - const isLatest$ = item.version.pipe( - getFirstSucceededRemoteDataPayload(), - map( (itemVersion) => this.versionHistoryService.isLatest$(itemVersion)) - ); - - */ - - /*map((item) => { - item.version.pipe( - getFirstSucceededRemoteDataPayload(), - switchMap( (itemVersion) => this.versionHistoryService.isLatest$(itemVersion)), - ).subscribe((isLatestVersion) => { - isDeletingLatestVersion = isLatestVersion; - } - ); - return item; - }),*/ - /*mergeMap((versionItem) => combineLatest([ - of(versionItem).pipe( - map((item) => item.id), - switchMap((itemId) => this.itemService.delete(itemId)), - getFirstCompletedRemoteData() - ), - versionHistory$.pipe( - getFirstSucceededRemoteDataPayload(), - ) - ])),*/ - - - // FUNZIONANTE: - - /*version.item.pipe(getFirstSucceededRemoteDataPayload()).subscribe((getItemRes) => { - const itemId = getItemRes.id; - - const isLatest$ = this.itemService.findById(itemId, true,true, followLink('version')).pipe( - getFirstSucceededRemoteDataPayload(), - switchMap( (item) => item.version ), - getFirstSucceededRemoteDataPayload(), - map( (itemVersion) => this.versionHistoryService.isLatest$(itemVersion)) - ); - - this.itemService.delete(itemId).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})); - } - } - ); - });*/ - }); } @@ -372,6 +443,33 @@ export class ItemVersionsComponent implements OnInit { }); } + // TODO eliminare + /*hasDraftVersion$(versionItem: Observable>): Observable { + return versionItem.pipe( + getFirstSucceededRemoteDataPayload(), + map((res) => res._links.version.href ), + switchMap( (res) => this.versionHistoryService.hasDraftVersion$(res)) + ); + }*/ + + canEditVersion$(versionItem: Version) { + return this.authorizationService.isAuthorized(FeatureID.CanEditVersion, versionItem.self); + } + + canDeleteVersion$(versionItem: Version) { + return this.authorizationService.isAuthorized(FeatureID.CanDeleteVersion, versionItem.self); + } + + // TODO eliminare (usa item anziché vrsion) + /*canDeleteVersion$(version: Version) { + return version.item.pipe( + getFirstSucceededRemoteDataPayload(), + map((item) => item.self), + switchMap((url) => this.authorizationService.isAuthorized(FeatureID.CanDeleteVersion, url)), + take(1), + ); + }*/ + /** * Initialize all observables */ @@ -384,6 +482,15 @@ export class ItemVersionsComponent implements OnInit { hasValueOperator(), switchMap((version: Version) => version.versionhistory) ); + this.hasDraftVersion$ = this.versionHistoryRD$.pipe( + getFirstSucceededRemoteDataPayload(), + map((res) => res.draftVersion) + ); + this.canCreateVersion$ = this.authorizationService.isAuthorized(FeatureID.CanCreateVersion, this.item.self); + this.createVersionTitle$ = this.hasDraftVersion$.pipe( + take(1), + switchMap((res) => of(res ? 'item.version.history.table.action.hasDraft' : 'item.version.history.table.action.newVersion')) + ); const versionHistory$ = this.versionHistoryRD$.pipe( getAllSucceededRemoteData(), getRemoteDataPayload(), diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 16dbd7dba7..d640ade531 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1910,7 +1910,9 @@ "item.page.return": "Back", - "item.page.version": "Create new version", + "item.page.version.create": "Create new version", + + "item.page.version.hasDraft": "A new version cannot be created because there is an inprogress submission in the version history", "item.preview.dc.identifier.uri": "Identifier:", @@ -1992,6 +1994,8 @@ "item.version.history.table.action.deleteVersion": "Delete version", + "item.version.history.table.action.hasDraft": "A new version cannot be created because there is an inprogress submission in the version history", + "item.version.notice": "This is not the latest version of this item. The latest version can be found here.",