[CST-4499] Version history - New version refactored - Test TBD

This commit is contained in:
Davide Negretti
2021-09-25 00:59:19 +02:00
parent c816b97525
commit f60755b2b0
13 changed files with 253 additions and 126 deletions

View File

@@ -32,6 +32,7 @@ import { MediaViewerVideoComponent } from './media-viewer/media-viewer-video/med
import { MediaViewerImageComponent } from './media-viewer/media-viewer-image/media-viewer-image.component';
import { NgxGalleryModule } from '@kolkov/ngx-gallery';
import { VersionPageComponent } from './version-page/version-page/version-page.component';
import { VersionedItemComponent } from './simple/item-types/versioned-item/versioned-item.component';
const ENTRY_COMPONENTS = [
// put only entry components that use custom decorator
@@ -77,7 +78,8 @@ const DECLARATIONS = [
NgxGalleryModule,
],
declarations: [
...DECLARATIONS
...DECLARATIONS,
VersionedItemComponent
],
exports: [
...DECLARATIONS

View File

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

View File

@@ -1,18 +1,8 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Item } from '../../../../core/shared/item.model';
import { ItemComponent } from '../shared/item.component';
import { ViewMode } from '../../../../core/shared/view-mode.model';
import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { VersionHistoryDataService } from '../../../../core/data/version-history-data.service';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
import { VersionDataService } from '../../../../core/data/version-data.service';
import { ItemVersionsSummaryModalComponent } from '../../../../shared/item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component';
import { getFirstCompletedRemoteData } from '../../../../core/shared/operators';
import { take } from 'rxjs/operators';
import { RemoteData } from '../../../../core/data/remote-data';
import { Version } from '../../../../core/shared/version.model';
import { VersionedItemComponent } from '../versioned-item/versioned-item.component';
/**
* Component that represents a publication Item page
@@ -25,59 +15,6 @@ import { Version } from '../../../../core/shared/version.model';
templateUrl: './untyped-item.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UntypedItemComponent extends ItemComponent {
private activeModal: NgbModalRef;
export class UntypedItemComponent extends VersionedItemComponent {
constructor(
private modalService: NgbModal,
private versionHistoryService: VersionHistoryDataService,
private notificationsService: NotificationsService,
private translateService: TranslateService,
// private itemService: ItemDataService,
private versionService: VersionDataService,
) {
super();
}
createNewVersionNotify(success: boolean, newVersionNumber: number) {
const successMessageKey = 'item.version.create.notification.success';
const failureMessageKey = 'item.version.create.notification.failure';
if (success) {
this.notificationsService.success(null, this.translateService.get(successMessageKey, {version: newVersionNumber}));
} else {
this.notificationsService.error(null, this.translateService.get(failureMessageKey));
}
}
createNewVersionSubmit(item: Item, summary: string) {
const itemHref = item._links.self.href;
this.versionHistoryService.createVersion(itemHref, summary).pipe(
take(1)
).subscribe((postResult) => {
this.createNewVersionNotify(postResult.hasSucceeded, postResult?.payload.version);
});
}
createNewVersionModal() {
const item = this.object;
// Open modal
this.activeModal = this.modalService.open(ItemVersionsSummaryModalComponent);
// Show current version in modal
this.versionService.findByHref(item._links.version.href).pipe(
getFirstCompletedRemoteData()
).subscribe(
(res: RemoteData<Version>) => {
// if response is empty then the item is unversioned
this.activeModal.componentInstance.firstVersion = res.hasNoContent;
this.activeModal.componentInstance.versionNumber = (res.hasNoContent ? undefined : res.payload.version);
}
);
// On modal submit create version
this.activeModal.result.then((modalResult) => {
this.createNewVersionSubmit(item, modalResult);
});
}
}

View File

@@ -0,0 +1 @@
<p>versioned-item works!</p>

View File

@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { VersionedItemComponent } from './versioned-item.component';
describe('VersionedItemComponent', () => {
let component: VersionedItemComponent;
let fixture: ComponentFixture<VersionedItemComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ VersionedItemComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(VersionedItemComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,55 @@
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 { RemoteData } from '../../../../core/data/remote-data';
import { Version } from '../../../../core/shared/version.model';
import { switchMap, take } 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';
@Component({
selector: 'ds-versioned-item',
templateUrl: './versioned-item.component.html',
styleUrls: ['./versioned-item.component.scss']
})
export class VersionedItemComponent extends ItemComponent {
constructor(
private modalService: NgbModal,
private versionHistoryService: VersionHistoryDataService,
private translateService: TranslateService,
private versionService: VersionDataService,
private itemVersionShared: ItemVersionsSharedService,
) {
super();
}
/**
* Open a modal that allows to create a new version starting from the specified item, with optional summary
*/
onCreateNewVersion(): void {
const item = this.object;
const versionHref = item._links.version.href;
// Open modal
const activeModal = this.modalService.open(ItemVersionsSummaryModalComponent);
// Show current version in modal
this.versionService.findByHref(versionHref).pipe(getFirstCompletedRemoteData()).subscribe((res: RemoteData<Version>) => {
// if res.hasNoContent then the item is unversioned
activeModal.componentInstance.firstVersion = res.hasNoContent;
activeModal.componentInstance.versionNumber = (res.hasNoContent ? undefined : res.payload.version);
});
// On createVersionEvent emitted create new version and notify
activeModal.componentInstance.createVersionEvent.pipe(
switchMap((summary: string) => this.itemVersionShared.createNewVersionAndNotify(item, summary)),
take(1)
).subscribe();
}
}

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { ItemVersionsSharedService } from './item-versions-shared.service';
describe('ItemVersionsSharedService', () => {
let service: ItemVersionsSharedService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ItemVersionsSharedService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@@ -0,0 +1,50 @@
import { Injectable } from '@angular/core';
import { NotificationsService } from '../../notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
import { Item } from '../../../core/shared/item.model';
import { switchMap } from 'rxjs/operators';
import { VersionHistoryDataService } from '../../../core/data/version-history-data.service';
import { Observable, of } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data';
import { Version } from '../../../core/shared/version.model';
@Injectable({
providedIn: 'root'
})
export class ItemVersionsSharedService {
constructor(
private notificationsService: NotificationsService,
private translateService: TranslateService,
private versionHistoryService: VersionHistoryDataService,
) {
}
private static msg(key: string): string {
const translationPrefix = 'item.version.create.notification';
return translationPrefix + '.' + key;
}
/**
* Create a new version, notify success/failure and return new version number
*
* @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> {
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);
})
);
}
private createNewVersionNotify(success: boolean, 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')));
}
}

View File

@@ -7,24 +7,30 @@
<div class="modal-body">
<p class="pb-2">
{{ "item.version.create.modal.text" | translate }}
<span *ngIf="!firstVersion">{{ "item.version.create.modal.text.startingFrom" | translate : {version: versionNumber} }}</span>
<span *ngIf="!firstVersion">
{{ "item.version.create.modal.text.startingFrom" | translate : {version: versionNumber} }}
</span>
</p>
<div class="form-group">
<label for="summary">{{'item.version.create.modal.form.summary.label' | translate }}:</label>
<input type="text" id="summary" required class="form-control" [(ngModel)]="newVersionSummary"
<input type="text" id="summary" class="form-control" [(ngModel)]="newVersionSummary"
(keyup.enter)="onModalSubmit()"
placeholder="{{'item.version.create.modal.form.summary.placeholder' | translate }}"/>
<!-- (keyup.enter)="$event.preventDefault(); $event.stopImmediatePropagation()"-->
</div>
</div>
<div class="modal-footer">
<button class="btn btn-outline-secondary btn-sm"
type="button"
(click)="onModalClose()"
title="{{'item.version.create.modal.button.cancel.tooltip' | translate}}">
<i class="fas fa-times fa-fw"></i> {{'item.version.create.modal.button.cancel' | translate}}
</button>
<button class="btn btn-success btn-sm"
type="submit"
(click)="onModalSubmit()"
title="{{'item.version.create.modal.button.confirm.tooltip' | translate}}">
<i class="fas fa-check fa-fw"></i> {{'item.version.create.modal.button.confirm' | translate}}
</button>
</button>
</div>
</div>

View File

@@ -1,4 +1,4 @@
import { Component } from '@angular/core';
import { Component, EventEmitter, Output } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
@Component({
@@ -12,17 +12,22 @@ export class ItemVersionsSummaryModalComponent {
newVersionSummary: string;
firstVersion = true;
@Output() createVersionEvent: EventEmitter<string> = new EventEmitter<string>();
constructor(
protected activeModal: NgbActiveModal,
) {
}
onModalClose() {
console.log('onModalClose() dismiss modal');
this.activeModal.dismiss();
}
onModalSubmit() {
this.activeModal.close(this.newVersionSummary);
console.log('onModalSubmit() emits \'' + this.newVersionSummary + '\'');
this.createVersionEvent.emit(this.newVersionSummary);
this.activeModal.close();
}
}

View File

@@ -11,7 +11,7 @@ import { VersionHistoryDataService } from '../../../core/data/version-history-da
import { By } from '@angular/platform-browser';
import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils';
import { createPaginatedList } from '../../testing/utils.test';
import { of as observableOf } from 'rxjs';
import { of, of as observableOf } from 'rxjs';
import { PaginationService } from '../../../core/pagination/pagination.service';
import { PaginationServiceStub } from '../../testing/pagination-service.stub';
import { AuthService } from '../../../core/auth/auth.service';
@@ -21,12 +21,14 @@ import { FormBuilder } from '@angular/forms';
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';
describe('ItemVersionsComponent', () => {
fdescribe('ItemVersionsComponent', () => {
let component: ItemVersionsComponent;
let fixture: ComponentFixture<ItemVersionsComponent>;
let authenticationService: AuthService;
let authorizationService: AuthorizationDataService;
let versionHistoryService: VersionHistoryDataService;
const versionHistory = Object.assign(new VersionHistory(), {
id: '1'
@@ -37,16 +39,25 @@ describe('ItemVersionsComponent', () => {
version: 1,
created: new Date(2020, 1, 1),
summary: 'first version',
versionhistory: createSuccessfulRemoteDataObject$(versionHistory)
versionhistory: createSuccessfulRemoteDataObject$(versionHistory),
_links: {
self: {
href: 'version2-url',
},
},
});
const version2 = Object.assign(new Version(), {
id: '2',
version: 2,
summary: 'second version',
created: new Date(2020, 1, 2),
versionhistory: createSuccessfulRemoteDataObject$(versionHistory)
versionhistory: createSuccessfulRemoteDataObject$(versionHistory),
_links: {
self: {
href: 'version2-url',
},
},
});
const versions = [version1, version2];
versionHistory.versions = createSuccessfulRemoteDataObject$(createPaginatedList(versions));
@@ -74,42 +85,46 @@ describe('ItemVersionsComponent', () => {
version1.item = createSuccessfulRemoteDataObject$(item1);
version2.item = createSuccessfulRemoteDataObject$(item2);
const versionHistoryService = jasmine.createSpyObj('versionHistoryService', {
const versionHistoryServiceSpy = jasmine.createSpyObj('versionHistoryService', {
getVersions: createSuccessfulRemoteDataObject$(createPaginatedList(versions))
});
const authenticationServiceSpy = jasmine.createSpyObj('authenticationService', {
isAuthenticated: observableOf(true),
setRedirectUrl: {}
}
);
const authorizationServiceSpy = jasmine.createSpyObj('authorizationService', ['isAuthorized']);
beforeEach(waitForAsync(() => {
authenticationService = jasmine.createSpyObj('authenticationService', {
isAuthenticated: observableOf(true),
setRedirectUrl: {}
}
);
authorizationService = jasmine.createSpyObj('authorizationService', {
isAuthorized: observableOf(true),
}
);
TestBed.configureTestingModule({
declarations: [ItemVersionsComponent, VarDirective],
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])],
providers: [
{ provide: PaginationService, useValue: new PaginationServiceStub() },
{ provide: FormBuilder, useValue: new FormBuilder() },
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
{ provide: AuthService, useValue: authenticationService },
{ provide: AuthorizationDataService, useValue: authorizationService },
{ provide: VersionHistoryDataService, useValue: versionHistoryService },
{ provide: ItemDataService, useValue: {} },
{ provide: VersionDataService, useValue: {} },
{provide: PaginationService, useValue: new PaginationServiceStub()},
{provide: FormBuilder, useValue: new FormBuilder()},
{provide: NotificationsService, useValue: new NotificationsServiceStub()},
{provide: AuthService, useValue: authenticationServiceSpy},
{provide: AuthorizationDataService, useValue: authorizationServiceSpy},
{provide: VersionHistoryDataService, useValue: versionHistoryServiceSpy},
{provide: ItemDataService, useValue: {}},
{provide: VersionDataService, useValue: {}},
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
versionHistoryService = TestBed.inject(VersionHistoryDataService);
authenticationService = TestBed.inject(AuthService);
authorizationService = TestBed.inject(AuthorizationDataService);
}));
beforeEach(() => {
fixture = TestBed.createComponent(ItemVersionsComponent);
component = fixture.componentInstance;
component.item = item1;
// component.displayActions = true;
fixture.detectChanges();
});
@@ -153,4 +168,27 @@ describe('ItemVersionsComponent', () => {
expect(summary.nativeElement.textContent).toEqual(version.summary);
});
});
describe('when the user can only delete a version', () => {
beforeAll(waitForAsync(() => {
const canDelete = (featureID: FeatureID, url: string ) => of(featureID === FeatureID.CanDeleteVersion);
authorizationServiceSpy.isAuthorized.and.callFake(canDelete);
}));
beforeEach(() => {
component.displayActions = true;
});
it('should not disable the delete button', () => {
const rows = fixture.debugElement.queryAll(By.css('tbody tr'));
expect(rows.length).toBe(versions.length);
console.log(rows);
const btn = fixture.debugElement.query(By.css(`.version-row-element-delete`));
console.log(btn);
});
it('should disable other buttons', () => {
// expect
});
});
});

View File

@@ -44,6 +44,7 @@ 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';
import { ItemVersionsSharedService } from './item-versions-shared.service';
@Component({
selector: 'ds-item-versions',
@@ -167,7 +168,8 @@ export class ItemVersionsComponent implements OnInit {
private notificationsService: NotificationsService,
private translateService: TranslateService,
private router: Router,
protected authorizationService: AuthorizationDataService,
private itemVersionShared: ItemVersionsSharedService,
private authorizationService: AuthorizationDataService,
) {
}
@@ -324,41 +326,31 @@ export class ItemVersionsComponent implements OnInit {
* @param version the version from which a new one will be created
*/
createNewVersion(version: Version) {
const successMessageKey = 'item.version.create.notification.success';
const failureMessageKey = 'item.version.create.notification.failure';
const versionNumber = version.version;
// Open modal
// Open modal and set current version number
const activeModal = this.modalService.open(ItemVersionsSummaryModalComponent);
activeModal.componentInstance.versionNumber = versionNumber;
// On modal submit/dismiss
activeModal.result.then((modalResult) => {
// TODO spostare in metodo
// TODO recuperare version history e invalidare cache
// this.versionHistoryService.invalidateVersionHistoryCache(versionHistory.id);
const summary = modalResult;
version.item.pipe(getFirstSucceededRemoteDataPayload()).subscribe((item) => {
const itemHref = item._links.self.href;
this.versionHistoryService.createVersion(itemHref, summary).pipe(take(1)).subscribe((postResult) => {
if (postResult.hasSucceeded) {
const newVersionNumber = postResult.payload.version;
this.notificationsService.success(null, this.translateService.get(successMessageKey, {version: newVersionNumber}));
console.log(version);
const versionHistory$ = this.versionService.getHistoryFromVersion$(version).pipe(
tap((res) => {
this.versionHistoryService.invalidateVersionHistoryCache(res.id);
}),
);
this.getAllVersions(versionHistory$);
} else {
this.notificationsService.error(null, this.translateService.get(failureMessageKey));
}
});
});
});
// On createVersionEvent emitted create new version and notify
activeModal.componentInstance.createVersionEvent.pipe(
mergeMap((summary: string) => combineLatest([
of(summary),
version.item.pipe(getFirstSucceededRemoteDataPayload())
])),
mergeMap(([summary, item]: [string, Item]) => this.itemVersionShared.createNewVersionAndNotify(item, summary)),
map((hasSucceeded: boolean) => {
if (hasSucceeded) {
const versionHistory$ = this.versionService.getHistoryFromVersion$(version).pipe(
tap((res) => {
this.versionHistoryService.invalidateVersionHistoryCache(res.id);
}),
);
this.getAllVersions(versionHistory$);
}
}),
take(1),
).subscribe();
}
/**