From e4d468b17c7e920c2a0e004dccf0ff7eaf2beb9d Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 15 Oct 2021 19:13:15 +0200 Subject: [PATCH] [CST-4499] Version history - Changes to version history table --- src/app/core/data/item-data.service.ts | 10 ++ src/app/core/data/version-data.service.ts | 4 +- .../core/data/version-history-data.service.ts | 8 ++ .../submission/workflowitem-data.service.ts | 25 +++- .../versioned-item.component.ts | 6 +- .../item-versions-shared.service.spec.ts | 4 + .../item-versions.component.html | 134 +++++++++++------- .../item-versions.component.spec.ts | 19 ++- .../item-versions/item-versions.component.ts | 44 ++++-- src/assets/i18n/en.json5 | 3 + 10 files changed, 189 insertions(+), 68 deletions(-) diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index 7a0116fe86..c31b6b3c97 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -59,6 +59,7 @@ export class ItemDataService extends DataService { * Get the endpoint for browsing items * (When options.sort.field is empty, the default field to browse by will be 'dc.date.issued') * @param {FindListOptions} options + * @param linkPath * @returns {Observable} */ public getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable { @@ -287,4 +288,13 @@ export class ItemDataService extends DataService { switchMap((url: string) => this.halService.getEndpoint('bitstreams', `${url}/${itemId}`)) ); } + + /** + * Invalidate the cache of the item + * @param itemUUID + */ + invalidateItemCache(itemUUID: string) { + this.requestService.setStaleByHrefSubstring('item/' + itemUUID); + } + } diff --git a/src/app/core/data/version-data.service.ts b/src/app/core/data/version-data.service.ts index 1c6bc9e702..f310b9a530 100644 --- a/src/app/core/data/version-data.service.ts +++ b/src/app/core/data/version-data.service.ts @@ -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, of } from 'rxjs'; +import { EMPTY, Observable } from 'rxjs'; import { dataService } from '../cache/builders/build-decorators'; import { VERSION } from '../shared/version.resource-type'; import { VersionHistory } from '../shared/version-history.model'; @@ -55,7 +55,7 @@ export class VersionDataService extends DataService { getFirstSucceededRemoteDataPayload(), switchMap((res) => res.versionhistory), getFirstSucceededRemoteDataPayload(), - ) : of(null); + ) : EMPTY; } /** diff --git a/src/app/core/data/version-history-data.service.ts b/src/app/core/data/version-history-data.service.ts index 86c678335c..ef537b72aa 100644 --- a/src/app/core/data/version-history-data.service.ts +++ b/src/app/core/data/version-history-data.service.ts @@ -196,6 +196,10 @@ export class VersionHistoryDataService extends DataService { ); } + /** + * Get the item of the latest version from any version in the version history + * @param version + */ getVersionHistoryFromVersion$(version: Version): Observable { return this.versionDataService.getHistoryIdFromVersion$(version).pipe( take(1), @@ -204,6 +208,10 @@ export class VersionHistoryDataService extends DataService { ); } + /** + * Invalidate the cache of the version history + * @param versionHistoryID + */ invalidateVersionHistoryCache(versionHistoryID: string) { this.requestService.setStaleByHrefSubstring('versioning/versionhistories/' + versionHistoryID); } diff --git a/src/app/core/submission/workflowitem-data.service.ts b/src/app/core/submission/workflowitem-data.service.ts index 099cfa8627..384d477110 100644 --- a/src/app/core/submission/workflowitem-data.service.ts +++ b/src/app/core/submission/workflowitem-data.service.ts @@ -9,7 +9,7 @@ import { DataService } from '../data/data.service'; import { RequestService } from '../data/request.service'; import { WorkflowItem } from './models/workflowitem.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { DeleteByIDRequest } from '../data/request.models'; +import { DeleteByIDRequest, FindListOptions } from '../data/request.models'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { ObjectCacheService } from '../cache/object-cache.service'; import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service'; @@ -19,6 +19,9 @@ import { hasValue } from '../../shared/empty.util'; import { RemoteData } from '../data/remote-data'; import { NoContent } from '../shared/NoContent.model'; import { getFirstCompletedRemoteData } from '../shared/operators'; +import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; +import { WorkspaceItem } from './models/workspaceitem.model'; +import { RequestParam } from '../cache/models/request-param.model'; /** * A service that provides methods to make REST requests with workflow items endpoint. @@ -27,6 +30,7 @@ import { getFirstCompletedRemoteData } from '../shared/operators'; @dataService(WorkflowItem.type) export class WorkflowItemDataService extends DataService { protected linkPath = 'workflowitems'; + protected searchByItemLinkPath = 'item'; protected responseMsToLive = 10 * 1000; constructor( @@ -86,4 +90,23 @@ export class WorkflowItemDataService extends DataService { return this.rdbService.buildFromRequestUUID(requestId); } + + /** + * Return the WorkflowItem object found through the UUID of an item + * + * @param uuid The uuid of the item + * @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's + * no valid cached version. Defaults to true + * @param reRequestOnStale Whether or not the request should automatically be re- + * requested after the response becomes stale + * @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, useCachedVersionIfAvailable = false, reRequestOnStale = true, options: FindListOptions = {}, ...linksToFollow: FollowLinkConfig[]): Observable> { + const findListOptions = new FindListOptions(); + findListOptions.searchParams = [new RequestParam('uuid', encodeURIComponent(uuid))]; + const href$ = this.getSearchByHref(this.searchByItemLinkPath, findListOptions, ...linksToFollow); + return this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); + } + } diff --git a/src/app/item-page/simple/item-types/versioned-item/versioned-item.component.ts b/src/app/item-page/simple/item-types/versioned-item/versioned-item.component.ts index 5eabf49610..45c15177e7 100644 --- a/src/app/item-page/simple/item-types/versioned-item/versioned-item.component.ts +++ b/src/app/item-page/simple/item-types/versioned-item/versioned-item.component.ts @@ -31,7 +31,7 @@ export class VersionedItemComponent extends ItemComponent { private versionService: VersionDataService, private itemVersionShared: ItemVersionsSharedService, private router: Router, - private workspaceitemDataService: WorkspaceitemDataService, + private workspaceItemDataService: WorkspaceitemDataService, private searchService: SearchService, private itemService: ItemDataService, ) { @@ -62,11 +62,11 @@ export class VersionedItemComponent extends ItemComponent { getFirstCompletedRemoteData(), // show success/failure notification tap((res: RemoteData) => { this.itemVersionShared.notifyCreateNewVersion(res); }), - getFirstSucceededRemoteDataPayload(), // get workspace item + getFirstSucceededRemoteDataPayload(), switchMap((newVersion: Version) => this.itemService.findByHref(newVersion._links.item.href)), getFirstSucceededRemoteDataPayload(), - switchMap((newVersionItem: Item) => this.workspaceitemDataService.findByItem(newVersionItem.uuid, true, false)), + switchMap((newVersionItem: Item) => this.workspaceItemDataService.findByItem(newVersionItem.uuid, true, false)), getFirstSucceededRemoteDataPayload(), ).subscribe((wsItem) => { const wsiId = wsItem.id; diff --git a/src/app/shared/item/item-versions/item-versions-shared.service.spec.ts b/src/app/shared/item/item-versions/item-versions-shared.service.spec.ts index e9aa2cc18a..2cf88dc3eb 100644 --- a/src/app/shared/item/item-versions/item-versions-shared.service.spec.ts +++ b/src/app/shared/item/item-versions/item-versions-shared.service.spec.ts @@ -7,6 +7,8 @@ import { AuthService } from '../../../core/auth/auth.service'; import { NotificationsService } from '../../notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; import { VersionHistoryDataService } from '../../../core/data/version-history-data.service'; +import { WorkspaceitemDataService } from '../../../core/submission/workspaceitem-data.service'; +import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service'; describe('ItemVersionsSharedService', () => { let service: ItemVersionsSharedService; @@ -20,6 +22,8 @@ describe('ItemVersionsSharedService', () => { { provide: AuthService, useValue: {} }, { provide: NotificationsService, useValue: {} }, { provide: TranslateService, useValue: {} }, + { provide: WorkspaceitemDataService, useValue: {} }, + { provide: WorkflowItemDataService, useValue: {} }, ], }); service = TestBed.inject(ItemVersionsSharedService); 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 7e8a376962..70954d7949 100644 --- a/src/app/shared/item/item-versions/item-versions.component.html +++ b/src/app/shared/item/item-versions/item-versions.component.html @@ -19,26 +19,72 @@ {{"item.version.history.table.editor" | translate}} {{"item.version.history.table.date" | translate}} {{"item.version.history.table.summary" | translate}} - {{"item.version.history.table.actions" | translate}} -
- -
- - - -
-
+ + + + +
+ + + + {{version.version}} + + + {{version.version}} + + * + + + {{ "item.version.history.table.workspaceItem" | translate }} + + + + {{ "item.version.history.table.workflowItem" | translate }} + + +
+ +
+ +
+ + + + + + + + + + +
+ +
+ +
+
@@ -49,14 +95,25 @@ {{version?.created | date : 'yyyy-MM-dd HH:mm:ss'}} - - {{version?.summary}} - - - - - -
+
+ + {{version?.summary}} + + + +
+ +
+ + + + - - - - - - - - - - - -
+ + diff --git a/src/app/shared/item/item-versions/item-versions.component.spec.ts b/src/app/shared/item/item-versions/item-versions.component.spec.ts index d6e3a96d19..35b16fad7c 100644 --- a/src/app/shared/item/item-versions/item-versions.component.spec.ts +++ b/src/app/shared/item/item-versions/item-versions.component.spec.ts @@ -22,6 +22,8 @@ import { NotificationsService } from '../../notifications/notifications.service' import { NotificationsServiceStub } from '../../testing/notifications-service.stub'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; +import { WorkspaceitemDataService } from '../../../core/submission/workspaceitem-data.service'; +import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service'; describe('ItemVersionsComponent', () => { let component: ItemVersionsComponent; @@ -29,9 +31,12 @@ describe('ItemVersionsComponent', () => { let authenticationService: AuthService; let authorizationService: AuthorizationDataService; let versionHistoryService: VersionHistoryDataService; + let workspaceItemDataService: WorkspaceitemDataService; + let workflowItemDataService: WorkflowItemDataService; const versionHistory = Object.assign(new VersionHistory(), { - id: '1' + id: '1', + draftVersion: false, }); const version1 = Object.assign(new Version(), { @@ -86,7 +91,7 @@ describe('ItemVersionsComponent', () => { version2.item = createSuccessfulRemoteDataObject$(item2); const versionHistoryServiceSpy = jasmine.createSpyObj('versionHistoryService', { - getVersions: createSuccessfulRemoteDataObject$(createPaginatedList(versions)) + getVersions: createSuccessfulRemoteDataObject$(createPaginatedList(versions)), }); const authenticationServiceSpy = jasmine.createSpyObj('authenticationService', { isAuthenticated: observableOf(true), @@ -94,6 +99,12 @@ describe('ItemVersionsComponent', () => { } ); const authorizationServiceSpy = jasmine.createSpyObj('authorizationService', ['isAuthorized']); + const workspaceItemDataServiceSpy = jasmine.createSpyObj('workspaceItemDataService', { + findByItem: of(undefined), + }); + const workflowItemDataServiceSpy = jasmine.createSpyObj('workflowItemDataService', { + findByItem: of(undefined), + }); beforeEach(waitForAsync(() => { @@ -110,6 +121,8 @@ describe('ItemVersionsComponent', () => { {provide: VersionHistoryDataService, useValue: versionHistoryServiceSpy}, {provide: ItemDataService, useValue: {}}, {provide: VersionDataService, useValue: {}}, + {provide: WorkspaceitemDataService, useValue: workspaceItemDataServiceSpy}, + {provide: WorkflowItemDataService, useValue: workflowItemDataServiceSpy}, ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); @@ -117,6 +130,8 @@ describe('ItemVersionsComponent', () => { versionHistoryService = TestBed.inject(VersionHistoryDataService); authenticationService = TestBed.inject(AuthService); authorizationService = TestBed.inject(AuthorizationDataService); + workspaceItemDataService = TestBed.inject(WorkspaceitemDataService); + workflowItemDataService = TestBed.inject(WorkflowItemDataService); })); 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 a1367c180a..8ecfc76ae8 100644 --- a/src/app/shared/item/item-versions/item-versions.component.ts +++ b/src/app/shared/item/item-versions/item-versions.component.ts @@ -8,7 +8,7 @@ import { combineLatest as observableCombineLatest, Observable, of, - Subscription + Subscription, } from 'rxjs'; import { VersionHistory } from '../../../core/shared/version-history.model'; import { @@ -47,6 +47,7 @@ import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; import { ItemVersionsSharedService } from './item-versions-shared.service'; import { WorkspaceItem } from '../../../core/submission/models/workspaceitem.model'; import { WorkspaceitemDataService } from '../../../core/submission/workspaceitem-data.service'; +import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service'; @Component({ selector: 'ds-item-versions', @@ -173,6 +174,7 @@ export class ItemVersionsComponent implements OnInit { private itemVersionShared: ItemVersionsSharedService, private authorizationService: AuthorizationDataService, private workspaceItemDataService: WorkspaceitemDataService, + private workflowItemDataService: WorkflowItemDataService, ) { } @@ -333,19 +335,31 @@ export class ItemVersionsComponent implements OnInit { of(summary), version.item.pipe(getFirstSucceededRemoteDataPayload()) ])), - mergeMap(([summary, item]: [string, Item]) => this.itemVersionShared.createNewVersionAndNotify(item, summary)), - map((newVersionRD: RemoteData) => { + mergeMap(([summary, item]: [string, Item]) => this.versionHistoryService.createVersion(item._links.self.href, summary)), + // show success/failure notification + tap((newVersionRD: RemoteData) => { + this.itemVersionShared.notifyCreateNewVersion(newVersionRD); if (newVersionRD.hasSucceeded) { const versionHistory$ = this.versionService.getHistoryFromVersion$(version).pipe( - tap((res: VersionHistory) => { - this.versionHistoryService.invalidateVersionHistoryCache(res.id); + tap((versionHistory: VersionHistory) => { + this.itemService.invalidateItemCache(this.item.uuid); + this.versionHistoryService.invalidateVersionHistoryCache(versionHistory.id); }), ); this.getAllVersions(versionHistory$); } }), - take(1), - ).subscribe(); + // get workspace item + getFirstSucceededRemoteDataPayload(), + switchMap((newVersion: Version) => this.itemService.findByHref(newVersion._links.item.href)), + getFirstSucceededRemoteDataPayload(), + switchMap((newVersionItem: Item) => this.workspaceItemDataService.findByItem(newVersionItem.uuid, true, false)), + getFirstSucceededRemoteDataPayload(), + ).subscribe((wsItem) => { + const wsiId = wsItem.id; + const route = 'workspaceitems/' + wsiId + '/edit'; + this.router.navigateByUrl(route); + }); } /** @@ -386,7 +400,7 @@ export class ItemVersionsComponent implements OnInit { * Get the ID of the workspace item, if present, otherwise return undefined * @param versionItem the item for which retrieve the workspace item id */ - getDraftId(versionItem): Observable { + getWorkspaceId(versionItem): Observable { return versionItem.pipe( getFirstSucceededRemoteDataPayload(), map((item: Item) => item.uuid), @@ -396,6 +410,20 @@ export class ItemVersionsComponent implements OnInit { ); } + /** + * Get the ID of the workflow item, if present, otherwise return undefined + * @param versionItem the item for which retrieve the workspace item id + */ + getWorkflowId(versionItem): Observable { + return versionItem.pipe( + getFirstSucceededRemoteDataPayload(), + map((item: Item) => item.uuid), + switchMap((itemUuid: string) => this.workflowItemDataService.findByItem(itemUuid, true)), + getFirstCompletedRemoteData(), + map((res: RemoteData) => res?.payload?.id ), + ); + } + /** * redirect to the edit page of the workspace item * @param id$ the id of the workspace item diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index bbaa570e8b..69d115bd2a 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1983,8 +1983,11 @@ "item.version.history.table.workspaceItem": "Workspace item", + "item.version.history.table.workflowItem": "Workflow item", + "item.version.history.table.actions": "Action", + "item.version.history.table.action.editWorkspaceItem": "Edit workspace item", "item.version.history.table.action.editSummary": "Edit summary",