[CST-4499] Version history - Redirect to Edit Item

This commit is contained in:
Davide Negretti
2021-09-29 16:33:23 +02:00
parent 4f2697bf52
commit ca9ca0105e
12 changed files with 124 additions and 55 deletions

View File

@@ -11,7 +11,7 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
import { HttpClient } from '@angular/common/http';
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
import { FindListOptions } from './request.models';
import { Observable } from 'rxjs';
import { Observable, of } from 'rxjs';
import { dataService } from '../cache/builders/build-decorators';
import { VERSION } from '../shared/version.resource-type';
import { VersionHistory } from '../shared/version-history.model';
@@ -51,11 +51,11 @@ export class VersionDataService extends DataService<Version> {
* @param version
*/
getHistoryFromVersion$(version: Version): Observable<VersionHistory> {
return this.findById(version.id, false, true, followLink('versionhistory')).pipe(
return version ? this.findById(version.id, false, true, followLink('versionhistory')).pipe(
getFirstSucceededRemoteDataPayload(),
switchMap((res) => res.versionhistory),
getFirstSucceededRemoteDataPayload(),
);
) : of(null);
}
/**

View File

@@ -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, startWith, switchMap, take } from 'rxjs/operators';
import { filter, map, 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';
@@ -138,40 +138,50 @@ export class VersionHistoryDataService extends DataService<VersionHistory> {
}
/**
* Get the latest version
* Get the latest version (return null if the specified version is null)
* @param version
*/
getLatestVersion$(version: Version): Observable<Version> {
// retrieve again version, including with versionHistory
return this.versionDataService.findById(version.id, false, true, followLink('versionhistory')).pipe(
return version.id ? this.versionDataService.findById(version.id, false, true, followLink('versionhistory')).pipe(
getFirstSucceededRemoteDataPayload(),
switchMap((res) => res.versionhistory),
getFirstSucceededRemoteDataPayload(),
switchMap((versionHistoryRD) => this.getLatestVersionFromHistory$(versionHistoryRD)),
);
) : of(null);
}
/**
* Check if the given version is the latest
* Check if the given version is the latest (return null if `version` is null)
* @param version
* @returns `true` if the specified version is the latest one, `false` otherwise, or `null` if the specified version is null
*/
isLatest$(version: Version): Observable<boolean> {
return this.getLatestVersion$(version).pipe(
return version ? this.getLatestVersion$(version).pipe(
take(1),
switchMap((latestVersion) => of(version.version === latestVersion.version))
);
) : of(null);
}
/**
* Check if a worskpace item exists in the version history
* Check if a worskpace item exists in the version history (return null if there is no version history)
* @param versionHref the href of the version
* @returns `true` if a workspace item exists, `false` otherwise, or `null` if a version history does not exist
*/
hasDraftVersion$(versionHref: string): Observable<boolean> {
return this.versionDataService.findByHref(versionHref, true, true, followLink('versionhistory')).pipe(
getFirstSucceededRemoteDataPayload(),
switchMap((version) => this.versionDataService.getHistoryFromVersion$(version)),
map((versionHistory) => versionHistory.draftVersion),
startWith(false),
take(1),
switchMap((res) => {
if (res.hasSucceeded && !res.hasNoContent) {
return of(res).pipe(
getFirstSucceededRemoteDataPayload(),
switchMap((version) => this.versionDataService.getHistoryFromVersion$(version)),
map((versionHistory) => versionHistory ? versionHistory.draftVersion : false),
);
} else {
return of(false);
}
}),
);
}

View File

@@ -12,6 +12,11 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
import { ObjectCacheService } from '../cache/object-cache.service';
import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
import { WorkspaceItem } from './models/workspaceitem.model';
import { Observable } from 'rxjs';
import { RemoteData } from '../data/remote-data';
import { FindListOptions } from '../data/request.models';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { RequestParam } from '../cache/models/request-param.model';
/**
* A service that provides methods to make REST requests with workspaceitems endpoint.
@@ -20,6 +25,7 @@ import { WorkspaceItem } from './models/workspaceitem.model';
@dataService(WorkspaceItem.type)
export class WorkspaceitemDataService extends DataService<WorkspaceItem> {
protected linkPath = 'workspaceitems';
protected searchByItemLinkPath = 'item';
constructor(
protected comparator: DSOChangeAnalyzer<WorkspaceItem>,
@@ -33,4 +39,18 @@ export class WorkspaceitemDataService extends DataService<WorkspaceItem> {
super();
}
/**
* Return the WorkspaceItem object found through the UUID of an item
*
* @param uuid The uuid of the item
* @param options The {@link FindListOptions} object
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
*/
public findByItem(uuid: string, options: FindListOptions = {}, ...linksToFollow: FollowLinkConfig<WorkspaceItem>[]): Observable<RemoteData<WorkspaceItem>> {
const findListOptions = new FindListOptions();
findListOptions.searchParams = [new RequestParam('uuid', encodeURIComponent(uuid))];
const href$ = this.getSearchByHref(this.searchByItemLinkPath, findListOptions, ...linksToFollow);
return this.findByHref(href$, false, true, ...linksToFollow);
}
}

View File

@@ -1,15 +1,21 @@
import { Component } from '@angular/core';
import { ItemComponent } from '../shared/item.component';
import { ItemVersionsSummaryModalComponent } from '../../../../shared/item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component';
import { getFirstCompletedRemoteData } from '../../../../core/shared/operators';
import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators';
import { RemoteData } from '../../../../core/data/remote-data';
import { Version } from '../../../../core/shared/version.model';
import { switchMap, take } from 'rxjs/operators';
import { switchMap } from 'rxjs/operators';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { VersionHistoryDataService } from '../../../../core/data/version-history-data.service';
import { TranslateService } from '@ngx-translate/core';
import { VersionDataService } from '../../../../core/data/version-data.service';
import { ItemVersionsSharedService } from '../../../../shared/item/item-versions/item-versions-shared.service';
import { Router } from '@angular/router';
import { WorkspaceitemDataService } from '../../../../core/submission/workspaceitem-data.service';
import { SearchService } from '../../../../core/shared/search/search.service';
import { Item } from '../../../../core/shared/item.model';
import { ItemDataService } from '../../../../core/data/item-data.service';
import { WorkspaceItem } from '../../../../core/submission/models/workspaceitem.model';
@Component({
selector: 'ds-versioned-item',
@@ -24,6 +30,10 @@ export class VersionedItemComponent extends ItemComponent {
private translateService: TranslateService,
private versionService: VersionDataService,
private itemVersionShared: ItemVersionsSharedService,
private router: Router,
private workspaceitemDataService: WorkspaceitemDataService,
private searchService: SearchService,
private itemService: ItemDataService,
) {
super();
}
@@ -49,7 +59,17 @@ export class VersionedItemComponent extends ItemComponent {
// On createVersionEvent emitted create new version and notify
activeModal.componentInstance.createVersionEvent.pipe(
switchMap((summary: string) => this.itemVersionShared.createNewVersionAndNotify(item, summary)),
take(1)
).subscribe();
getFirstSucceededRemoteDataPayload<Version>(),
switchMap((newVersion: Version) => this.itemService.findByHref(newVersion._links.item.href)),
getFirstSucceededRemoteDataPayload<Item>(),
switchMap((newVersionItem: Item) => this.workspaceitemDataService.findByItem(newVersionItem.uuid)),
getFirstSucceededRemoteDataPayload<WorkspaceItem>(),
).subscribe((wsItem) => {
const wsiId = wsItem.id;
console.log(wsiId);
const route = 'workspaceitems/' + wsiId + '/edit';
this.router.navigateByUrl(route);
});
}
}

View File

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

View File

@@ -4,7 +4,7 @@ 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 { map, startWith, switchMap } from 'rxjs/operators';
import { of } from 'rxjs';
@Component({
@@ -43,7 +43,7 @@ export class DsoPageVersionButtonComponent implements OnInit {
*/
isAuthorized$: Observable<boolean>;
hasDraftVersion$: Observable<boolean>;
disableNewVersionButton$: Observable<boolean>;
tooltipMsg$: Observable<string>;
@@ -62,8 +62,16 @@ export class DsoPageVersionButtonComponent implements OnInit {
ngOnInit() {
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(
console.log('href = ' + this.dso._links.version.href);
this.disableNewVersionButton$ = this.versionHistoryService.hasDraftVersion$(this.dso._links.version.href).pipe(
// button is disabled if hasDraftVersion = true, and enabled if hasDraftVersion = false or null
// (hasDraftVersion is null when a version history does not exist)
map((res) => Boolean(res)),
startWith(true),
);
this.tooltipMsg$ = this.disableNewVersionButton$.pipe(
switchMap((hasDraftVersion) => of(hasDraftVersion ? this.tooltipMsgHasDraft : this.tooltipMsgCreate)),
);
}

View File

@@ -31,20 +31,20 @@ export class ItemVersionsSharedService {
* @param item the item from which a new version will be created
* @param summary the optional summary for the new version
*/
createNewVersionAndNotify(item: Item, summary: string): Observable<boolean> {
createNewVersionAndNotify(item: Item, summary: string): Observable<RemoteData<Version>> {
return this.versionHistoryService.createVersion(item._links.self.href, summary).pipe(
switchMap((postResult: RemoteData<Version>) => {
const newVersionNumber = postResult?.payload.version;
this.createNewVersionNotify(postResult.hasSucceeded, newVersionNumber);
return of(postResult.hasSucceeded);
const newVersionNumber = postResult?.payload?.version;
this.notifyCreateNewVersion(postResult.hasSucceeded, postResult.statusCode, newVersionNumber);
return of(postResult);
})
);
}
private createNewVersionNotify(success: boolean, newVersionNumber: number) {
private notifyCreateNewVersion(success: boolean, statusCode: number, newVersionNumber: number) {
success ?
this.notificationsService.success(null, this.translateService.get(ItemVersionsSharedService.msg('success'), {version: newVersionNumber})) :
this.notificationsService.error(null, this.translateService.get(ItemVersionsSharedService.msg('failure')));
this.notificationsService.error(null, this.translateService.get(ItemVersionsSharedService.msg(statusCode === 422 ? 'inProgress' : 'failure')));
}
}

View File

@@ -20,12 +20,10 @@ export class ItemVersionsSummaryModalComponent {
}
onModalClose() {
console.log('onModalClose() dismiss modal');
this.activeModal.dismiss();
}
onModalSubmit() {
console.log('onModalSubmit() emits \'' + this.newVersionSummary + '\'');
this.createVersionEvent.emit(this.newVersionSummary);
this.activeModal.close();
}

View File

@@ -223,7 +223,9 @@ export class ItemVersionsComponent implements OnInit {
const successMessageKey = 'item.version.edit.notification.success';
const failureMessageKey = 'item.version.edit.notification.failure';
this.versionService.findById(this.versionBeingEditedId).pipe(getFirstSucceededRemoteData()).subscribe(
this.versionService.findById(this.versionBeingEditedId).pipe(
getFirstSucceededRemoteData(),
).subscribe(
(findRes) => {
const updatedVersion =
Object.assign({}, findRes.payload, {
@@ -231,9 +233,16 @@ export class ItemVersionsComponent implements OnInit {
});
this.versionService.update(updatedVersion).pipe(take(1)).subscribe(
(updateRes) => {
// TODO check
if (updateRes.hasSucceeded) {
this.notificationsService.success(null, this.translateService.get(successMessageKey, {'version': this.versionBeingEditedNumber}));
// const versionHistory$ = this.versionHistoryRD$.pipe(
// getAllSucceededRemoteData(),
// getRemoteDataPayload(),
// hasValueOperator(),
// );
// this.getAllVersions(versionHistory$);
} else {
this.notificationsService.warning(null, this.translateService.get(failureMessageKey, {'version': this.versionBeingEditedNumber}));
}
@@ -257,7 +266,7 @@ export class ItemVersionsComponent implements OnInit {
}
/**
* Deletes the specified version
* Deletes the specified version, notify the success/failure and redirect to latest version
* @param version the version to be deleted
* @param redirectToLatest force the redirect to the latest version in the history
*/
@@ -274,32 +283,25 @@ export class ItemVersionsComponent implements OnInit {
// On modal submit/dismiss
activeModal.result.then(() => {
// let versionHistoryOuter: VersionHistory;
versionItem$.pipe(
getFirstSucceededRemoteDataPayload<Item>(),
// Retrieve version history and invalidate cache
mergeMap((item: Item) => combineLatest([
// pass item
of(item),
// get and return version history
this.versionHistoryService.getVersionHistoryFromVersion$(version).pipe(
// invalidate cache
tap((versionHistory) => {
this.versionHistoryService.invalidateVersionHistoryCache(versionHistory.id);
})
)
])),
// Delete item
mergeMap(([item, versionHistory]: [Item, VersionHistory]) => combineLatest([
// delete item and return result
this.deleteItemAndGetResult$(item),
// pass version history
of(versionHistory)
])),
// Retrieve new latest version
mergeMap(([deleteItemResult, versionHistory]: [boolean, VersionHistory]) => combineLatest([
// pass result
of(deleteItemResult),
// get and return new latest version
this.versionHistoryService.getLatestVersionItemFromHistory$(versionHistory).pipe(
tap(() => {
this.getAllVersions(of(versionHistory));
@@ -307,12 +309,12 @@ export class ItemVersionsComponent implements OnInit {
)
])),
).subscribe(([deleteHasSucceeded, newLatestVersionItem]) => {
// Notify operation result and redirect to latest item
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 path = getItemEditVersionhistoryRoute(newLatestVersionItem);
this.router.navigateByUrl(path);
@@ -339,8 +341,8 @@ export class ItemVersionsComponent implements OnInit {
version.item.pipe(getFirstSucceededRemoteDataPayload())
])),
mergeMap(([summary, item]: [string, Item]) => this.itemVersionShared.createNewVersionAndNotify(item, summary)),
map((hasSucceeded: boolean) => {
if (hasSucceeded) {
map((newVersionRD: RemoteData<Version>) => {
if (newVersionRD.hasSucceeded) {
const versionHistory$ = this.versionService.getHistoryFromVersion$(version).pipe(
tap((res) => {
this.versionHistoryService.invalidateVersionHistoryCache(res.id);
@@ -394,10 +396,18 @@ export class ItemVersionsComponent implements OnInit {
if (hasValue(this.item.version)) {
this.versionRD$ = this.item.version;
this.versionHistoryRD$ = this.versionRD$.pipe(
// switchMap( (res) => {
// if (res.hasFailed) {
// return of(createFailedRemoteDataObject<VersionHistory>());
// } else {
// return of(res).pipe(
getAllSucceededRemoteData(),
getRemoteDataPayload(),
hasValueOperator(),
switchMap((version: Version) => version.versionhistory)
switchMap((version: Version) => version.versionhistory),
// );
// }
// }),
);
this.canCreateVersion$ = this.authorizationService.isAuthorized(FeatureID.CanCreateVersion, this.item.self);
@@ -405,7 +415,7 @@ export class ItemVersionsComponent implements OnInit {
// If there is a draft item in the version history the 'Create version' button is disabled and a different tooltip message is shown
this.hasDraftVersion$ = this.versionHistoryRD$.pipe(
getFirstSucceededRemoteDataPayload(),
map((res) => res.draftVersion)
map((res) => Boolean(res?.draftVersion)),
);
this.createVersionTitle$ = this.hasDraftVersion$.pipe(
take(1),

View File

@@ -1,4 +1,4 @@
<ds-alert *ngIf="isLatestVersion$ && !(isLatestVersion$ | async)"
<ds-alert *ngIf="showLatestVersionNotice$ && (showLatestVersionNotice$ | async)"
[content]="('item.version.notice' | translate:{ destination: getItemPage(((latestVersion$ | async)?.item | async)?.payload) })"
[dismissible]="false"
[type]="AlertTypeEnum.Warning">

View File

@@ -10,7 +10,7 @@ import {
getFirstSucceededRemoteDataPayload,
getRemoteDataPayload
} from '../../../../core/shared/operators';
import { startWith, switchMap } from 'rxjs/operators';
import { map, startWith, switchMap } from 'rxjs/operators';
import { VersionHistoryDataService } from '../../../../core/data/version-history-data.service';
import { AlertType } from '../../../alert/aletr-type';
import { getItemPageRoute } from '../../../../item-page/item-page-routing-paths';
@@ -48,7 +48,7 @@ export class ItemVersionsNoticeComponent implements OnInit {
* Is the item's version equal to the latest version from the version history?
* This will determine whether or not to display a notice linking to the latest version
*/
isLatestVersion$: Observable<boolean>;
showLatestVersionNotice$: Observable<boolean>;
/**
* Pagination options to fetch a single version on the first page (this is the latest version in the history)
@@ -81,10 +81,11 @@ export class ItemVersionsNoticeComponent implements OnInit {
switchMap((vh) => this.versionHistoryService.getLatestVersionFromHistory$(vh))
);
this.isLatestVersion$ = this.versionRD$.pipe(
this.showLatestVersionNotice$ = this.versionRD$.pipe(
getFirstSucceededRemoteDataPayload(),
switchMap((version) => this.versionHistoryService.isLatest$(version)),
startWith(true),
map((isLatest) => isLatest != null && !isLatest),
startWith(false),
);
}
}

View File

@@ -2022,6 +2022,8 @@
"item.version.create.notification.failure" : "New version has not been created",
"item.version.create.notification.inProgress" : "A new version cannot be created because there is an inprogress submission in the version history",
"item.version.delete.modal.header": "Delete version",