mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge branch 'master' into w2p-68346_Bundles-in-edit-item-Updates
Conflicts: src/app/core/core.module.ts src/app/shared/shared.module.ts
This commit is contained in:
@@ -1128,6 +1128,12 @@
|
||||
|
||||
"item.edit.tabs.status.title": "Item Edit - Status",
|
||||
|
||||
"item.edit.tabs.versionhistory.head": "Version History",
|
||||
|
||||
"item.edit.tabs.versionhistory.title": "Item Edit - Version History",
|
||||
|
||||
"item.edit.tabs.versionhistory.under-construction": "Editing or adding new versions is not yet possible in this user interface.",
|
||||
|
||||
"item.edit.tabs.view.head": "View Item",
|
||||
|
||||
"item.edit.tabs.view.title": "Item Edit - View",
|
||||
@@ -1207,6 +1213,25 @@
|
||||
"item.select.table.title": "Title",
|
||||
|
||||
|
||||
"item.version.history.empty": "There are no other versions for this item yet.",
|
||||
|
||||
"item.version.history.head": "Version History",
|
||||
|
||||
"item.version.history.return": "Return",
|
||||
|
||||
"item.version.history.selected": "Selected version",
|
||||
|
||||
"item.version.history.table.version": "Version",
|
||||
|
||||
"item.version.history.table.item": "Item",
|
||||
|
||||
"item.version.history.table.editor": "Editor",
|
||||
|
||||
"item.version.history.table.date": "Date",
|
||||
|
||||
"item.version.history.table.summary": "Summary",
|
||||
|
||||
|
||||
|
||||
"journal.listelement.badge": "Journal",
|
||||
|
||||
|
@@ -29,6 +29,7 @@ import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||
import { ItemEditBitstreamDragHandleComponent } from './item-bitstreams/item-edit-bitstream-drag-handle/item-edit-bitstream-drag-handle.component';
|
||||
import { PaginatedDragAndDropBitstreamListComponent } from './item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component';
|
||||
import { VirtualMetadataComponent } from './virtual-metadata/virtual-metadata.component';
|
||||
import { ItemVersionHistoryComponent } from './item-version-history/item-version-history.component';
|
||||
|
||||
/**
|
||||
* Module that contains all components related to the Edit Item page administrator functionality
|
||||
@@ -56,6 +57,7 @@ import { VirtualMetadataComponent } from './virtual-metadata/virtual-metadata.co
|
||||
ItemMetadataComponent,
|
||||
ItemRelationshipsComponent,
|
||||
ItemBitstreamsComponent,
|
||||
ItemVersionHistoryComponent,
|
||||
EditInPlaceFieldComponent,
|
||||
ItemEditBitstreamComponent,
|
||||
ItemEditBitstreamBundleComponent,
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import { ItemPageResolver } from '../item-page.resolver';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { EditItemPageComponent } from './edit-item-page.component';
|
||||
@@ -14,6 +13,7 @@ import { ItemCollectionMapperComponent } from './item-collection-mapper/item-col
|
||||
import { ItemMoveComponent } from './item-move/item-move.component';
|
||||
import { ItemRelationshipsComponent } from './item-relationships/item-relationships.component';
|
||||
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { ItemVersionHistoryComponent } from './item-version-history/item-version-history.component';
|
||||
|
||||
export const ITEM_EDIT_WITHDRAW_PATH = 'withdraw';
|
||||
export const ITEM_EDIT_REINSTATE_PATH = 'reinstate';
|
||||
@@ -75,6 +75,11 @@ export const ITEM_EDIT_MOVE_PATH = 'move';
|
||||
/* TODO - change when curate page exists */
|
||||
component: ItemBitstreamsComponent,
|
||||
data: { title: 'item.edit.tabs.curate.title', showBreadcrumbs: true }
|
||||
},
|
||||
{
|
||||
path: 'versionhistory',
|
||||
component: ItemVersionHistoryComponent,
|
||||
data: { title: 'item.edit.tabs.versionhistory.title', showBreadcrumbs: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@@ -0,0 +1,6 @@
|
||||
<div class="mt-4">
|
||||
<ds-alert [content]="'item.edit.tabs.versionhistory.under-construction'" [type]="AlertTypeEnum.Warning"></ds-alert>
|
||||
</div>
|
||||
<div class="mt-2" *ngVar="(itemRD$ | async)?.payload as item">
|
||||
<ds-item-versions *ngIf="item" [item]="item" [displayWhenEmpty]="true" [displayTitle]="false"></ds-item-versions>
|
||||
</div>
|
@@ -0,0 +1,44 @@
|
||||
import { ItemVersionHistoryComponent } from './item-version-history.component';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { VarDirective } from '../../../shared/utils/var.directive';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { createSuccessfulRemoteDataObject } from '../../../shared/testing/utils';
|
||||
|
||||
describe('ItemVersionHistoryComponent', () => {
|
||||
let component: ItemVersionHistoryComponent;
|
||||
let fixture: ComponentFixture<ItemVersionHistoryComponent>;
|
||||
|
||||
const item = Object.assign(new Item(), {
|
||||
uuid: 'item-identifier-1',
|
||||
handle: '123456789/1',
|
||||
});
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ItemVersionHistoryComponent, VarDirective],
|
||||
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])],
|
||||
providers: [
|
||||
{ provide: ActivatedRoute, useValue: { parent: { data: observableOf({ item: createSuccessfulRemoteDataObject(item) }) } } }
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ItemVersionHistoryComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should initialize the itemRD$ from the route\'s data', (done) => {
|
||||
component.itemRD$.subscribe((itemRD) => {
|
||||
expect(itemRD.payload).toBe(item);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,35 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { getSucceededRemoteData } from '../../../core/shared/operators';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { AlertType } from '../../../shared/alert/aletr-type';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-version-history',
|
||||
templateUrl: './item-version-history.component.html'
|
||||
})
|
||||
/**
|
||||
* Component for listing and managing an item's version history
|
||||
*/
|
||||
export class ItemVersionHistoryComponent {
|
||||
/**
|
||||
* The item to display the version history for
|
||||
*/
|
||||
itemRD$: Observable<RemoteData<Item>>;
|
||||
|
||||
/**
|
||||
* The AlertType enumeration
|
||||
* @type {AlertType}
|
||||
*/
|
||||
AlertTypeEnum = AlertType;
|
||||
|
||||
constructor(private route: ActivatedRoute) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.itemRD$ = this.route.parent.data.pipe(map((data) => data.item)).pipe(getSucceededRemoteData()) as Observable<RemoteData<Item>>;
|
||||
}
|
||||
}
|
@@ -21,6 +21,7 @@
|
||||
</table>
|
||||
<ds-item-page-full-file-section [item]="item"></ds-item-page-full-file-section>
|
||||
<ds-item-page-collections [item]="item"></ds-item-page-collections>
|
||||
<ds-item-versions class="mt-2" [item]="item"></ds-item-versions>
|
||||
</div>
|
||||
</div>
|
||||
<ds-error *ngIf="itemRD?.hasFailed" message="{{'error.item' | translate}}"></ds-error>
|
||||
|
@@ -61,7 +61,7 @@ import { AbstractIncrementalListComponent } from './simple/abstract-incremental-
|
||||
RelatedEntitiesSearchComponent,
|
||||
UploadBitstreamComponent,
|
||||
TabbedRelatedEntitiesSearchComponent,
|
||||
AbstractIncrementalListComponent
|
||||
AbstractIncrementalListComponent,
|
||||
],
|
||||
exports: [
|
||||
ItemComponent,
|
||||
|
@@ -28,6 +28,7 @@ export class ItemPageResolver implements Resolve<RemoteData<Item>> {
|
||||
followLink('owningCollection'),
|
||||
followLink('bundles'),
|
||||
followLink('relationships'),
|
||||
followLink('version', undefined, true, followLink('versionhistory')),
|
||||
).pipe(
|
||||
find((RD) => hasValue(RD.error) || RD.hasSucceeded),
|
||||
);
|
||||
|
@@ -3,6 +3,7 @@
|
||||
<div *ngIf="itemRD?.payload as item">
|
||||
<ds-view-tracker [object]="item"></ds-view-tracker>
|
||||
<ds-listable-object-component-loader [object]="item" [viewMode]="viewMode"></ds-listable-object-component-loader>
|
||||
<ds-item-versions class="mt-2" [item]="item"></ds-item-versions>
|
||||
</div>
|
||||
</div>
|
||||
<ds-error *ngIf="itemRD?.hasFailed" message="{{'error.item' | translate}}"></ds-error>
|
||||
|
@@ -144,6 +144,10 @@ import { PoolTaskDataService } from './tasks/pool-task-data.service';
|
||||
import { TaskResponseParsingService } from './tasks/task-response-parsing.service';
|
||||
import { ArrayMoveChangeAnalyzer } from './data/array-move-change-analyzer.service';
|
||||
import { BitstreamDataService } from './data/bitstream-data.service';
|
||||
import { VersionDataService } from './data/version-data.service';
|
||||
import { VersionHistoryDataService } from './data/version-history-data.service';
|
||||
import { Version } from './shared/version.model';
|
||||
import { VersionHistory } from './shared/version-history.model';
|
||||
|
||||
/**
|
||||
* When not in production, endpoint responses can be mocked for testing purposes
|
||||
@@ -259,6 +263,8 @@ const PROVIDERS = [
|
||||
RelationshipTypeService,
|
||||
ExternalSourceService,
|
||||
LookupRelationService,
|
||||
VersionDataService,
|
||||
VersionHistoryDataService,
|
||||
LicenseDataService,
|
||||
ItemTypeDataService,
|
||||
// register AuthInterceptor as HttpInterceptor
|
||||
@@ -309,6 +315,8 @@ export const models =
|
||||
ItemType,
|
||||
ExternalSource,
|
||||
ExternalSourceEntry,
|
||||
Version,
|
||||
VersionHistory
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
44
src/app/core/data/version-data.service.ts
Normal file
44
src/app/core/data/version-data.service.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { DataService } from './data.service';
|
||||
import { Version } from '../shared/version.model';
|
||||
import { RequestService } from './request.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
||||
import { FindListOptions } from './request.models';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { dataService } from '../cache/builders/build-decorators';
|
||||
import { VERSION } from '../shared/version.resource-type';
|
||||
|
||||
/**
|
||||
* Service responsible for handling requests related to the Version object
|
||||
*/
|
||||
@Injectable()
|
||||
@dataService(VERSION)
|
||||
export class VersionDataService extends DataService<Version> {
|
||||
protected linkPath = 'versions';
|
||||
|
||||
constructor(
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected store: Store<CoreState>,
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected halService: HALEndpointService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected http: HttpClient,
|
||||
protected comparator: DefaultChangeAnalyzer<Version>) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the endpoint for browsing versions
|
||||
*/
|
||||
getBrowseEndpoint(options: FindListOptions = {}, linkPath?: string): Observable<string> {
|
||||
return this.halService.getEndpoint(this.linkPath);
|
||||
}
|
||||
}
|
54
src/app/core/data/version-history-data.service.spec.ts
Normal file
54
src/app/core/data/version-history-data.service.spec.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { RequestService } from './request.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { VersionHistoryDataService } from './version-history-data.service';
|
||||
import { NotificationsServiceStub } from '../../shared/testing/notifications-service-stub';
|
||||
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub';
|
||||
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
|
||||
import { GetRequest } from './request.models';
|
||||
|
||||
const url = 'fake-url';
|
||||
|
||||
describe('VersionHistoryDataService', () => {
|
||||
let service: VersionHistoryDataService;
|
||||
|
||||
let requestService: RequestService;
|
||||
let notificationsService: any;
|
||||
let rdbService: RemoteDataBuildService;
|
||||
let objectCache: ObjectCacheService;
|
||||
let halService: any;
|
||||
|
||||
beforeEach(() => {
|
||||
createService();
|
||||
});
|
||||
|
||||
describe('getVersions', () => {
|
||||
let result;
|
||||
|
||||
beforeEach(() => {
|
||||
result = service.getVersions('1');
|
||||
});
|
||||
|
||||
it('should configure a GET request', () => {
|
||||
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(GetRequest));
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Create a VersionHistoryDataService used for testing
|
||||
* @param requestEntry$ Supply a requestEntry to be returned by the REST API (optional)
|
||||
*/
|
||||
function createService(requestEntry$?) {
|
||||
requestService = getMockRequestService(requestEntry$);
|
||||
rdbService = jasmine.createSpyObj('rdbService', {
|
||||
buildList: jasmine.createSpy('buildList')
|
||||
});
|
||||
objectCache = jasmine.createSpyObj('objectCache', {
|
||||
remove: jasmine.createSpy('remove')
|
||||
});
|
||||
halService = new HALEndpointServiceStub(url);
|
||||
notificationsService = new NotificationsServiceStub();
|
||||
|
||||
service = new VersionHistoryDataService(requestService, rdbService, null, objectCache, halService, notificationsService, null, null);
|
||||
}
|
||||
});
|
81
src/app/core/data/version-history-data.service.ts
Normal file
81
src/app/core/data/version-history-data.service.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { DataService } from './data.service';
|
||||
import { VersionHistory } from '../shared/version-history.model';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { RequestService } from './request.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
||||
import { FindListOptions, GetRequest } from './request.models';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { PaginatedList } from './paginated-list';
|
||||
import { Version } from '../shared/version.model';
|
||||
import { 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';
|
||||
|
||||
/**
|
||||
* Service responsible for handling requests related to the VersionHistory object
|
||||
*/
|
||||
@Injectable()
|
||||
@dataService(VERSION_HISTORY)
|
||||
export class VersionHistoryDataService extends DataService<VersionHistory> {
|
||||
protected linkPath = 'versionhistories';
|
||||
protected versionsEndpoint = 'versions';
|
||||
|
||||
constructor(
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected store: Store<CoreState>,
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected halService: HALEndpointService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected http: HttpClient,
|
||||
protected comparator: DefaultChangeAnalyzer<VersionHistory>) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the endpoint for browsing versions
|
||||
*/
|
||||
getBrowseEndpoint(options: FindListOptions = {}, linkPath?: string): Observable<string> {
|
||||
return this.halService.getEndpoint(this.linkPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the versions endpoint for a version history
|
||||
* @param versionHistoryId
|
||||
*/
|
||||
getVersionsEndpoint(versionHistoryId: string): Observable<string> {
|
||||
return this.getBrowseEndpoint().pipe(
|
||||
switchMap((href: string) => this.halService.getEndpoint(this.versionsEndpoint, `${href}/${versionHistoryId}`))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a version history's versions using paginated search options
|
||||
* @param versionHistoryId The version history's ID
|
||||
* @param searchOptions The search options to use
|
||||
* @param linksToFollow HAL Links to follow on the Versions
|
||||
*/
|
||||
getVersions(versionHistoryId: string, searchOptions?: PaginatedSearchOptions, ...linksToFollow: Array<FollowLinkConfig<Version>>): Observable<RemoteData<PaginatedList<Version>>> {
|
||||
const hrefObs = this.getVersionsEndpoint(versionHistoryId).pipe(
|
||||
map((href) => searchOptions ? searchOptions.toRestUrl(href) : href)
|
||||
);
|
||||
hrefObs.pipe(
|
||||
take(1)
|
||||
).subscribe((href) => {
|
||||
const request = new GetRequest(this.requestService.generateRequestId(), href);
|
||||
this.requestService.configure(request);
|
||||
});
|
||||
|
||||
return this.rdbService.buildList<Version>(hrefObs, ...linksToFollow);
|
||||
}
|
||||
}
|
@@ -18,6 +18,8 @@ import { Relationship } from './item-relationships/relationship.model';
|
||||
import { RELATIONSHIP } from './item-relationships/relationship.resource-type';
|
||||
import { ITEM } from './item.resource-type';
|
||||
import { ChildHALResource } from './child-hal-resource.model';
|
||||
import { Version } from './version.model';
|
||||
import { VERSION } from './version.resource-type';
|
||||
|
||||
/**
|
||||
* Class representing a DSpace Item
|
||||
@@ -67,6 +69,7 @@ export class Item extends DSpaceObject implements ChildHALResource {
|
||||
bundles: HALLink;
|
||||
owningCollection: HALLink;
|
||||
templateItemOf: HALLink;
|
||||
version: HALLink;
|
||||
self: HALLink;
|
||||
};
|
||||
|
||||
@@ -77,6 +80,13 @@ export class Item extends DSpaceObject implements ChildHALResource {
|
||||
@link(COLLECTION)
|
||||
owningCollection?: Observable<RemoteData<Collection>>;
|
||||
|
||||
/**
|
||||
* The version this item represents in its history
|
||||
* Will be undefined unless the version {@link HALLink} has been resolved.
|
||||
*/
|
||||
@link(VERSION)
|
||||
version?: Observable<RemoteData<Version>>;
|
||||
|
||||
/**
|
||||
* The list of Bundles inside this Item
|
||||
* Will be undefined unless the bundles {@link HALLink} has been resolved.
|
||||
|
39
src/app/core/shared/version-history.model.ts
Normal file
39
src/app/core/shared/version-history.model.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { deserialize, autoserialize, inheritSerialization } from 'cerialize';
|
||||
import { excludeFromEquals } from '../utilities/equals.decorators';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { PaginatedList } from '../data/paginated-list';
|
||||
import { Version } from './version.model';
|
||||
import { VERSION_HISTORY } from './version-history.resource-type';
|
||||
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';
|
||||
|
||||
/**
|
||||
* Class representing a DSpace Version History
|
||||
*/
|
||||
@typedObject
|
||||
@inheritSerialization(DSpaceObject)
|
||||
export class VersionHistory extends DSpaceObject {
|
||||
static type = VERSION_HISTORY;
|
||||
|
||||
@deserialize
|
||||
_links: {
|
||||
self: HALLink;
|
||||
versions: HALLink;
|
||||
};
|
||||
|
||||
/**
|
||||
* The identifier of this Version History
|
||||
*/
|
||||
@autoserialize
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The list of versions within this history
|
||||
*/
|
||||
@excludeFromEquals
|
||||
@link(VERSION, true)
|
||||
versions: Observable<RemoteData<PaginatedList<Version>>>;
|
||||
}
|
9
src/app/core/shared/version-history.resource-type.ts
Normal file
9
src/app/core/shared/version-history.resource-type.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { ResourceType } from './resource-type';
|
||||
|
||||
/**
|
||||
* The resource type for VersionHistory
|
||||
*
|
||||
* Needs to be in a separate file to prevent circular
|
||||
* dependencies in webpack.
|
||||
*/
|
||||
export const VERSION_HISTORY = new ResourceType('versionhistory');
|
76
src/app/core/shared/version.model.ts
Normal file
76
src/app/core/shared/version.model.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { deserialize, autoserialize, inheritSerialization } from 'cerialize';
|
||||
import { excludeFromEquals } from '../utilities/equals.decorators';
|
||||
import { Item } from './item.model';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { VersionHistory } from './version-history.model';
|
||||
import { EPerson } from '../eperson/models/eperson.model';
|
||||
import { VERSION } from './version.resource-type';
|
||||
import { HALLink } from './hal-link.model';
|
||||
import { link, typedObject } from '../cache/builders/build-decorators';
|
||||
import { VERSION_HISTORY } from './version-history.resource-type';
|
||||
import { ITEM } from './item.resource-type';
|
||||
import { EPERSON } from '../eperson/models/eperson.resource-type';
|
||||
import { DSpaceObject } from './dspace-object.model';
|
||||
|
||||
/**
|
||||
* Class representing a DSpace Version
|
||||
*/
|
||||
@typedObject
|
||||
@inheritSerialization(DSpaceObject)
|
||||
export class Version extends DSpaceObject {
|
||||
static type = VERSION;
|
||||
|
||||
@deserialize
|
||||
_links: {
|
||||
self: HALLink;
|
||||
item: HALLink;
|
||||
versionhistory: HALLink;
|
||||
eperson: HALLink;
|
||||
};
|
||||
|
||||
/**
|
||||
* The identifier of this Version
|
||||
*/
|
||||
@autoserialize
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The version number of the version's history this version represents
|
||||
*/
|
||||
@autoserialize
|
||||
version: number;
|
||||
|
||||
/**
|
||||
* The summary for the changes made in this version
|
||||
*/
|
||||
@autoserialize
|
||||
summary: string;
|
||||
|
||||
/**
|
||||
* The Date this version was created
|
||||
*/
|
||||
@deserialize
|
||||
created: Date;
|
||||
|
||||
/**
|
||||
* The full version history this version is apart of
|
||||
*/
|
||||
@excludeFromEquals
|
||||
@link(VERSION_HISTORY)
|
||||
versionhistory: Observable<RemoteData<VersionHistory>>;
|
||||
|
||||
/**
|
||||
* The item this version represents
|
||||
*/
|
||||
@excludeFromEquals
|
||||
@link(ITEM)
|
||||
item: Observable<RemoteData<Item>>;
|
||||
|
||||
/**
|
||||
* The e-person who created this version
|
||||
*/
|
||||
@excludeFromEquals
|
||||
@link(EPERSON)
|
||||
eperson: Observable<RemoteData<EPerson>>;
|
||||
}
|
9
src/app/core/shared/version.resource-type.ts
Normal file
9
src/app/core/shared/version.resource-type.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { ResourceType } from './resource-type';
|
||||
|
||||
/**
|
||||
* The resource type for Version
|
||||
*
|
||||
* Needs to be in a separate file to prevent circular
|
||||
* dependencies in webpack.
|
||||
*/
|
||||
export const VERSION = new ResourceType('version');
|
@@ -0,0 +1,47 @@
|
||||
<div *ngVar="(versionsRD$ | async)?.payload as versions">
|
||||
<div *ngVar="(versionRD$ | async)?.payload as itemVersion">
|
||||
<div class="mb-2" *ngIf="versions?.page?.length > 0 || displayWhenEmpty">
|
||||
<h2 *ngIf="displayTitle">{{"item.version.history.head" | translate}}</h2>
|
||||
<ds-pagination *ngIf="versions?.page?.length > 0"
|
||||
[hideGear]="true"
|
||||
[hidePagerWhenSinglePage]="true"
|
||||
[paginationOptions]="options"
|
||||
[pageInfoState]="versions"
|
||||
[collectionSize]="versions?.totalElements"
|
||||
[disableRouteParameterUpdate]="true"
|
||||
(pageChange)="switchPage($event)">
|
||||
<table class="table table-striped my-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{{"item.version.history.table.version" | translate}}</th>
|
||||
<th scope="col">{{"item.version.history.table.item" | translate}}</th>
|
||||
<th scope="col" *ngIf="(hasEpersons$ | async)">{{"item.version.history.table.editor" | translate}}</th>
|
||||
<th scope="col">{{"item.version.history.table.date" | translate}}</th>
|
||||
<th scope="col">{{"item.version.history.table.summary" | translate}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let version of versions?.page" [id]="'version-row-' + version.id">
|
||||
<td class="version-row-element-version">{{version?.version}}</td>
|
||||
<td class="version-row-element-item">
|
||||
<span *ngVar="(version?.item | async)?.payload as item">
|
||||
<a *ngIf="item" [routerLink]="['/items', item?.id]">{{item?.handle}}</a>
|
||||
<span *ngIf="version?.id === itemVersion?.id">*</span>
|
||||
</span>
|
||||
</td>
|
||||
<td *ngIf="(hasEpersons$ | async)" class="version-row-element-editor">
|
||||
<span *ngVar="(version?.eperson | async)?.payload as eperson">
|
||||
<a *ngIf="eperson" [href]="'mailto:' + eperson?.email">{{eperson?.name}}</a>
|
||||
</span>
|
||||
</td>
|
||||
<td class="version-row-element-date">{{version?.created}}</td>
|
||||
<td class="version-row-element-summary">{{version?.summary}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div>* {{"item.version.history.selected" | translate}}</div>
|
||||
</ds-pagination>
|
||||
<ds-alert *ngIf="!itemVersion || versions?.page?.length === 0" [content]="'item.version.history.empty'" [type]="AlertTypeEnum.Info"></ds-alert>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,121 @@
|
||||
import { ItemVersionsComponent } from './item-versions.component';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { VarDirective } from '../../utils/var.directive';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { Version } from '../../../core/shared/version.model';
|
||||
import { VersionHistory } from '../../../core/shared/version-history.model';
|
||||
import { createPaginatedList, createSuccessfulRemoteDataObject$ } from '../../testing/utils';
|
||||
import { VersionHistoryDataService } from '../../../core/data/version-history-data.service';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
describe('ItemVersionsComponent', () => {
|
||||
let component: ItemVersionsComponent;
|
||||
let fixture: ComponentFixture<ItemVersionsComponent>;
|
||||
|
||||
const versionHistory = Object.assign(new VersionHistory(), {
|
||||
id: '1'
|
||||
});
|
||||
const version1 = Object.assign(new Version(), {
|
||||
id: '1',
|
||||
version: 1,
|
||||
created: new Date(2020, 1, 1),
|
||||
summary: 'first version',
|
||||
versionhistory: createSuccessfulRemoteDataObject$(versionHistory)
|
||||
});
|
||||
const version2 = Object.assign(new Version(), {
|
||||
id: '2',
|
||||
version: 2,
|
||||
summary: 'second version',
|
||||
created: new Date(2020, 1, 2),
|
||||
versionhistory: createSuccessfulRemoteDataObject$(versionHistory)
|
||||
});
|
||||
const versions = [version1, version2];
|
||||
versionHistory.versions = createSuccessfulRemoteDataObject$(createPaginatedList(versions));
|
||||
const item1 = Object.assign(new Item(), {
|
||||
uuid: 'item-identifier-1',
|
||||
handle: '123456789/1',
|
||||
version: createSuccessfulRemoteDataObject$(version1)
|
||||
});
|
||||
const item2 = Object.assign(new Item(), {
|
||||
uuid: 'item-identifier-2',
|
||||
handle: '123456789/2',
|
||||
version: createSuccessfulRemoteDataObject$(version2)
|
||||
});
|
||||
const items = [item1, item2];
|
||||
version1.item = createSuccessfulRemoteDataObject$(item1);
|
||||
version2.item = createSuccessfulRemoteDataObject$(item2);
|
||||
const versionHistoryService = jasmine.createSpyObj('versionHistoryService', {
|
||||
getVersions: createSuccessfulRemoteDataObject$(createPaginatedList(versions))
|
||||
});
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ItemVersionsComponent, VarDirective],
|
||||
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])],
|
||||
providers: [
|
||||
{ provide: VersionHistoryDataService, useValue: versionHistoryService }
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ItemVersionsComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.item = item1;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it(`should display ${versions.length} rows`, () => {
|
||||
const rows = fixture.debugElement.queryAll(By.css('tbody tr'));
|
||||
expect(rows.length).toBe(versions.length);
|
||||
});
|
||||
|
||||
versions.forEach((version: Version, index: number) => {
|
||||
const versionItem = items[index];
|
||||
|
||||
it(`should display version ${version.version} in the correct column for version ${version.id}`, () => {
|
||||
const id = fixture.debugElement.query(By.css(`#version-row-${version.id} .version-row-element-version`));
|
||||
expect(id.nativeElement.textContent).toEqual('' + version.version);
|
||||
});
|
||||
|
||||
it(`should display item handle ${versionItem.handle} in the correct column for version ${version.id}`, () => {
|
||||
const item = fixture.debugElement.query(By.css(`#version-row-${version.id} .version-row-element-item`));
|
||||
expect(item.nativeElement.textContent).toContain(versionItem.handle);
|
||||
});
|
||||
|
||||
// This version's item is equal to the component's item (the selected item)
|
||||
// Check if the handle contains an asterisk
|
||||
if (item1.uuid === versionItem.uuid) {
|
||||
it('should add an asterisk to the handle of the selected item', () => {
|
||||
const item = fixture.debugElement.query(By.css(`#version-row-${version.id} .version-row-element-item`));
|
||||
expect(item.nativeElement.textContent).toContain('*');
|
||||
});
|
||||
}
|
||||
|
||||
it(`should display date ${version.created} in the correct column for version ${version.id}`, () => {
|
||||
const date = fixture.debugElement.query(By.css(`#version-row-${version.id} .version-row-element-date`));
|
||||
expect(date.nativeElement.textContent).toEqual('' + version.created);
|
||||
});
|
||||
|
||||
it(`should display summary ${version.summary} in the correct column for version ${version.id}`, () => {
|
||||
const summary = fixture.debugElement.query(By.css(`#version-row-${version.id} .version-row-element-summary`));
|
||||
expect(summary.nativeElement.textContent).toEqual(version.summary);
|
||||
});
|
||||
});
|
||||
|
||||
describe('switchPage', () => {
|
||||
const page = 5;
|
||||
|
||||
beforeEach(() => {
|
||||
component.switchPage(page);
|
||||
});
|
||||
|
||||
it('should set the option\'s currentPage to the new page', () => {
|
||||
expect(component.options.currentPage).toEqual(page);
|
||||
});
|
||||
});
|
||||
});
|
130
src/app/shared/item/item-versions/item-versions.component.ts
Normal file
130
src/app/shared/item/item-versions/item-versions.component.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
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 { Observable } from 'rxjs/internal/Observable';
|
||||
import { VersionHistory } from '../../../core/shared/version-history.model';
|
||||
import { getAllSucceededRemoteData, getRemoteDataPayload } from '../../../core/shared/operators';
|
||||
import { map, startWith, switchMap } from 'rxjs/operators';
|
||||
import { combineLatest as observableCombineLatest } from 'rxjs';
|
||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||
import { PaginationComponentOptions } from '../../pagination/pagination-component-options.model';
|
||||
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
|
||||
import { VersionHistoryDataService } from '../../../core/data/version-history-data.service';
|
||||
import { PaginatedSearchOptions } from '../../search/paginated-search-options.model';
|
||||
import { AlertType } from '../../alert/aletr-type';
|
||||
import { followLink } from '../../utils/follow-link-config.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-versions',
|
||||
templateUrl: './item-versions.component.html'
|
||||
})
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@Input() item: Item;
|
||||
|
||||
/**
|
||||
* An option to display the list of versions, even when there aren't any.
|
||||
* Instead of the table, an alert will be displayed, notifying the user there are no other versions present
|
||||
* for the current item.
|
||||
*/
|
||||
@Input() displayWhenEmpty = false;
|
||||
|
||||
/**
|
||||
* Whether or not to display the title
|
||||
*/
|
||||
@Input() displayTitle = true;
|
||||
|
||||
/**
|
||||
* The AlertType enumeration
|
||||
* @type {AlertType}
|
||||
*/
|
||||
AlertTypeEnum = AlertType;
|
||||
|
||||
/**
|
||||
* The item's version
|
||||
*/
|
||||
versionRD$: Observable<RemoteData<Version>>;
|
||||
|
||||
/**
|
||||
* The item's full version history
|
||||
*/
|
||||
versionHistoryRD$: Observable<RemoteData<VersionHistory>>;
|
||||
|
||||
/**
|
||||
* The version history's list of versions
|
||||
*/
|
||||
versionsRD$: Observable<RemoteData<PaginatedList<Version>>>;
|
||||
|
||||
/**
|
||||
* Verify if the list of versions has at least one e-person to display
|
||||
* Used to hide the "Editor" column when no e-persons are present to display
|
||||
*/
|
||||
hasEpersons$: Observable<boolean>;
|
||||
|
||||
/**
|
||||
* The amount of versions to display per page
|
||||
*/
|
||||
pageSize = 10;
|
||||
|
||||
/**
|
||||
* The page options to use for fetching the versions
|
||||
* Start at page 1 and always use the set page size
|
||||
*/
|
||||
options = Object.assign(new PaginationComponentOptions(),{
|
||||
id: 'item-versions-options',
|
||||
currentPage: 1,
|
||||
pageSize: this.pageSize
|
||||
});
|
||||
|
||||
/**
|
||||
* The current page being displayed
|
||||
*/
|
||||
currentPage$ = new BehaviorSubject<number>(1);
|
||||
|
||||
constructor(private versionHistoryService: VersionHistoryDataService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all observables
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.versionRD$ = this.item.version;
|
||||
this.versionHistoryRD$ = this.versionRD$.pipe(
|
||||
getAllSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
switchMap((version: Version) => version.versionhistory)
|
||||
);
|
||||
const versionHistory$ = this.versionHistoryRD$.pipe(
|
||||
getAllSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
);
|
||||
this.versionsRD$ = observableCombineLatest(versionHistory$, this.currentPage$).pipe(
|
||||
switchMap(([versionHistory, page]: [VersionHistory, number]) =>
|
||||
this.versionHistoryService.getVersions(versionHistory.id,
|
||||
new PaginatedSearchOptions({pagination: Object.assign({}, this.options, { currentPage: page })}),
|
||||
followLink('item'), followLink('eperson')))
|
||||
);
|
||||
this.hasEpersons$ = this.versionsRD$.pipe(
|
||||
getAllSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
map((versions: PaginatedList<Version>) => versions.page.filter((version: Version) => version.eperson !== undefined).length > 0),
|
||||
startWith(false)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current page
|
||||
* @param page
|
||||
*/
|
||||
switchPage(page: number) {
|
||||
this.options.currentPage = page;
|
||||
this.currentPage$.next(page);
|
||||
}
|
||||
|
||||
}
|
@@ -176,6 +176,7 @@ import { ExternalSourceEntryImportModalComponent } from './form/builder/ds-dynam
|
||||
import { ImportableListItemControlComponent } from './object-collection/shared/importable-list-item-control/importable-list-item-control.component';
|
||||
import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||
import { ExistingMetadataListElementComponent } from './form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component';
|
||||
import { ItemVersionsComponent } from './item/item-versions/item-versions.component';
|
||||
import { SortablejsModule } from 'ngx-sortablejs';
|
||||
import { CustomSwitchComponent } from './form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.component';
|
||||
import { BundleListElementComponent } from './object-list/bundle-list-element/bundle-list-element.component';
|
||||
@@ -349,6 +350,7 @@ const COMPONENTS = [
|
||||
ExternalSourceEntryImportModalComponent,
|
||||
ImportableListItemControlComponent,
|
||||
ExistingMetadataListElementComponent,
|
||||
ItemVersionsComponent,
|
||||
PublicationSearchResultListElementComponent,
|
||||
];
|
||||
|
||||
@@ -414,6 +416,7 @@ const ENTRY_COMPONENTS = [
|
||||
DsDynamicLookupRelationSelectionTabComponent,
|
||||
DsDynamicLookupRelationExternalSourceTabComponent,
|
||||
ExternalSourceEntryImportModalComponent,
|
||||
ItemVersionsComponent,
|
||||
BundleListElementComponent
|
||||
];
|
||||
|
||||
|
Reference in New Issue
Block a user