[CST-4504] Fixed multiple delete & multiple add in modal and changed unit testing

This commit is contained in:
Rezart Vata
2021-10-14 13:06:55 +02:00
parent ce2790a89b
commit 05261a91ea
13 changed files with 160 additions and 196 deletions

View File

@@ -141,7 +141,6 @@ export class ObjectUpdatesService {
map((objectEntry) => {
const fieldUpdates: FieldUpdates = {};
for (const object of initialFields) {
console.log(object.uuid,objectEntry.fieldUpdates);
let fieldUpdate = objectEntry.fieldUpdates[object.uuid];
if (isEmpty(fieldUpdate)) {
fieldUpdate = { field: object, changeType: undefined };

View File

@@ -497,6 +497,14 @@ export class RelationshipService extends DataService<Relationship> {
fieldName: 'size',
fieldValue: arrayOfItemIds.length
},
{
fieldName: 'embed',
fieldValue: 'leftItem'
},
{
fieldName: 'embed',
fieldValue: 'rightItem'
},
];
arrayOfItemIds.forEach( (itemId) => {

View File

@@ -1,6 +1,6 @@
<h5>
{{getRelationshipMessageKey() | async | translate}}
<button class="ml-2 btn btn-success" (click)="openLookup()">
<button class="ml-2 btn btn-success" [disabled]="(hasChanges | async)" (click)="openLookup()">
<i class="fas fa-plus"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.relationships.edit.buttons.add" | translate}}</span>
</button>

View File

@@ -59,6 +59,7 @@ describe('EditRelationshipListComponent', () => {
comp.itemType = entityType;
comp.url = url;
comp.relationshipType = relationshipType;
comp.hasChanges = observableOf(false);
fixture.detectChanges();
};
@@ -278,6 +279,24 @@ describe('EditRelationshipListComponent', () => {
expect(label).toEqual('isAuthorOfPublication');
});
});
describe('changes managment for add buttons', () => {
it('should show enabled add buttons', () => {
const element = de.query(By.css('.btn-success'));
expect(element.nativeElement?.disabled).toBeFalse();
});
it('after hash changes changed', () => {
comp.hasChanges = observableOf(true);
fixture.detectChanges();
const element = de.query(By.css('.btn-success'));
expect(element.nativeElement?.disabled).toBeTrue();
});
});
});
});

View File

@@ -81,32 +81,16 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
*/
@Input() relationshipType: RelationshipType;
/**
* If updated information has changed
*/
@Input() hasChanges!: Observable<boolean>;
/**
* If changes have been discarded and need reinstated
*/
@Input() isReinstatable!: Observable<boolean>;
/**
* The event emmiter to submit the new information
*/
@Output() submit: EventEmitter<any> = new EventEmitter();
/**
* The event emmiter to reinstate the discarded information
*/
@Output() reinstate: EventEmitter<any> = new EventEmitter();
/**
* The event emmiter to discard the new information
*/
@Output() discard: EventEmitter<any> = new EventEmitter();
/**
* Observable that emits the left and right item type of {@link relationshipType} simultaneously.
@@ -168,7 +152,6 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
modalRef: NgbModalRef;
constructor(
protected objectUpdatesService: ObjectUpdatesService,
protected linkService: LinkService,
@@ -244,26 +227,22 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
modalComp.item = this.item;
modalComp.relationshipType = this.relationshipType;
modalComp.currentItemIsLeftItem$ = this.currentItemIsLeftItem$;
modalComp.hasChanges = this.hasChanges;
modalComp.isReinstatable = this.isReinstatable;
modalComp.submit = this.submit;
modalComp.reinstate = this.reinstate;
modalComp.discard = this.discard;
modalComp.toAdd = [];
modalComp.toRemove = [];
modalComp.isPending = false;
this.item.owningCollection.pipe(
getFirstSucceededRemoteDataPayload()
).subscribe((collection: Collection) => {
modalComp.collection = collection;
});
modalComp.select = (...selectableObjects: SearchResult<Item>[]) => {
selectableObjects.forEach((searchResult) => {
const relatedItem: Item = searchResult.indexableObject;
const foundIndex = modalComp.toRemove.findIndex( el => el.uuid === relatedItem.uuid);
let foundIndex = modalComp.toRemove.findIndex( el => el.uuid == relatedItem.uuid);
console.log("select",foundIndex);
if (foundIndex !== -1) {
modalComp.toRemove.splice(foundIndex,1);
} else {
@@ -271,29 +250,14 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
this.getIsRelatedItem(relatedItem)
.subscribe((isRelated: boolean) => {
if (!isRelated) {
modalComp.toAdd.push(relatedItem);
// this.relationshipService.getNameVariant(this.listId, relatedItem.uuid)
// .subscribe((nameVariant) => {
// const update = {
// uuid: this.relationshipType.id + '-' + relatedItem.uuid,
// nameVariant,
// type: this.relationshipType,
// relatedItem,
// } as RelationshipIdentifiable;
// this.objectUpdatesService.saveAddFieldUpdate(this.url, update);
// });
}
this.loading$.next(true);
// emit the last page again to trigger a fieldupdates refresh
this.relationshipsRd$.next(this.relationshipsRd$.getValue());
});
}
});
};
@@ -301,35 +265,25 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
selectableObjects.forEach((searchResult) => {
const relatedItem: Item = searchResult.indexableObject;
let foundIndex = modalComp.toAdd.findIndex( el => el.uuid == relatedItem.uuid);
console.log("deselect",foundIndex);
const foundIndex = modalComp.toAdd.findIndex( el => el.uuid === relatedItem.uuid);
if (foundIndex !== -1) {
modalComp.toAdd.splice(foundIndex,1);
} else {
modalComp.toRemove.push(relatedItem);
}
// this.objectUpdatesService.removeSingleFieldUpdate(this.url, this.relationshipType.id + '-' + relatedItem.uuid);
// this.getFieldUpdatesForRelatedItem(relatedItem)
// .subscribe((identifiables) =>
// identifiables.forEach((identifiable) =>
// this.objectUpdatesService.saveRemoveFieldUpdate(this.url, identifiable)
// )
// );
});
// this.loading$.next(true);
// emit the last page again to trigger a fieldupdates refresh
// this.relationshipsRd$.next(this.relationshipsRd$.getValue());
};
// TODO: JOIN SUBSCRIPTION BEFORE SUBMIT
modalComp.submitEv = () => {
const subscriptions = [];
modalComp.toAdd.forEach((relatedItem) => {
this.relationshipService.getNameVariant(this.listId, relatedItem.uuid)
.subscribe((nameVariant) => {
subscriptions.push(this.relationshipService.getNameVariant(this.listId, relatedItem.uuid).pipe(
map((nameVariant) => {
const update = {
uuid: this.relationshipType.id + '-' + relatedItem.uuid,
nameVariant,
@@ -337,37 +291,46 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
relatedItem,
} as RelationshipIdentifiable;
this.objectUpdatesService.saveAddFieldUpdate(this.url, update);
});
return update;
})
));
});
modalComp.toRemove.forEach( (relatedItem) => {
this.relationshipService.getNameVariant(this.listId, relatedItem.uuid)
.subscribe((nameVariant) => {
subscriptions.push(this.relationshipService.getNameVariant(this.listId, relatedItem.uuid).pipe(
switchMap((nameVariant) => {
return this.getRelationFromId(relatedItem).pipe(
map( (relationship: Relationship) => {
const update = {
uuid: this.relationshipType.id + '-' + relatedItem.uuid,
uuid: relationship.id,
nameVariant,
type: this.relationshipType,
relatedItem,
relationship,
} as RelationshipIdentifiable;
this.objectUpdatesService.saveRemoveFieldUpdate(this.url,update);
return update;
})
);
})
));
});
// this.objectUpdatesService.removeSingleFieldUpdate(this.url, this.relationshipType.id + '-' + relatedItem.uuid);
// this.getFieldUpdatesForRelatedItem(relatedItem)
// .subscribe((identifiables) =>
// identifiables.forEach((identifiable) =>
// this.objectUpdatesService.saveRemoveFieldUpdate(this.url, identifiable)
// )
// );
});
observableCombineLatest(subscriptions).subscribe( (res) => {
// Wait until the states changes since there are multiple items
setTimeout( () => {
this.submit.emit();
this.modalRef.close();
},3000);
},1000);
}
modalComp.isPending = true;
});
};
modalComp.discardEv = () => {
modalComp.toAdd = [];
modalComp.toRemove = [];
};
this.relatedEntityType$
.pipe(take(1))
@@ -385,6 +348,26 @@ export class EditRelationshipListComponent implements OnInit, OnDestroy {
this.selectableListService.deselectAll(this.listId);
}
getRelationFromId(relatedItem) {
return this.currentItemIsLeftItem$.pipe(
take(1),
switchMap( isLeft => {
let apiCall;
if (isLeft) {
apiCall = this.relationshipService.searchByItemsAndType( this.relationshipType.id, this.item.uuid, this.relationshipType.leftwardType ,[relatedItem.id] );
} else {
apiCall = this.relationshipService.searchByItemsAndType( this.relationshipType.id, this.item.uuid, this.relationshipType.rightwardType ,[relatedItem.id] );
}
return apiCall.pipe(
map( (res: PaginatedList<Relationship[]>) => res.page[0])
);
}
));
}
/**
* Get the existing field updates regarding a relationship with a given item
* @param relatedItem The item for which to get the existing field updates

View File

@@ -79,7 +79,7 @@ export class EditRelationshipComponent implements OnChanges {
* Sets the current relationship based on the fieldUpdate input field
*/
ngOnChanges(): void {
if (this.relationship) {
if (this.relationship && (!!this.relationship.leftItem || !!this.relationship.rightItem)) {
this.leftItem$ = this.relationship.leftItem.pipe(
getFirstSucceededRemoteData(),
getRemoteDataPayload(),

View File

@@ -28,9 +28,6 @@
[itemType]="entityType$ | async"
[relationshipType]="relationshipType"
[hasChanges] = hasChanges()
[isReinstatable] = isReinstatable()
(discard) = discard()
(reinstate) = reinstate()
(submit) = submit()
></ds-edit-relationship-list>
</div>

View File

@@ -34,6 +34,7 @@ import { NoContent } from '../../../core/shared/NoContent.model';
import { hasValue } from '../../../shared/empty.util';
import { RelationshipTypeService } from '../../../core/data/relationship-type.service';
import { PaginatedList } from '../../../core/data/paginated-list.model';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
@Component({
selector: 'ds-item-relationships',
@@ -69,6 +70,7 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
public entityTypeService: EntityTypeService,
protected relationshipTypeService: RelationshipTypeService,
public cdr: ChangeDetectorRef,
protected modalService: NgbModal,
) {
super(itemService, objectUpdatesService, router, notificationsService, translateService, route);
}
@@ -108,10 +110,6 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
*/
public submit(): void {
this.relationshipService.getItemRelationshipsArray(this.item).pipe(
take(1)
).subscribe(res=>console.log(res));
// Get all the relationships that should be removed
const removedRelationshipIDs$: Observable<DeleteRelationship[]> = this.relationshipService.getItemRelationshipsArray(this.item).pipe(
startWith([]),
@@ -119,13 +117,12 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
Object.assign(new Relationship(), relationship, { uuid: relationship.id })
)),
switchMap((relationships: Relationship[]) => {
return this.objectUpdatesService.getFieldUpdatesAll(this.url, relationships) as Observable<FieldUpdates>;
return this.objectUpdatesService.getFieldUpdates(this.url, relationships) as Observable<FieldUpdates>;
}),
map((fieldUpdates: FieldUpdates) => {
console.log(fieldUpdates);
return Object.values(fieldUpdates)
.filter((fieldUpdate: FieldUpdate) => fieldUpdate.changeType === FieldChangeType.REMOVE)
.map((fieldUpdate: FieldUpdate) => fieldUpdate.field as DeleteRelationship)
.map((fieldUpdate: FieldUpdate) => fieldUpdate.field as DeleteRelationship);
}
),
);
@@ -155,6 +152,7 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
this.initializeOriginalFields();
this.cdr.detectChanges();
this.displayNotifications(response);
this.modalService.dismissAll();
}
})
);
@@ -162,7 +160,6 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
}
deleteRelationships(deleteRelationshipIDs: DeleteRelationship[]): Observable<RemoteData<NoContent>[]> {
console.log(deleteRelationshipIDs);
return observableZip(...deleteRelationshipIDs.map((deleteRelationship) => {
let copyVirtualMetadata: string;
if (deleteRelationship.keepLeftVirtualMetadata && deleteRelationship.keepRightVirtualMetadata) {

View File

@@ -1,7 +1,7 @@
<div class="modal-header">
<h4 class="modal-title" id="modal-title">{{ ('submission.sections.describe.relationship-lookup.title.' + relationshipOptions.relationshipType) | translate }}</h4>
<button type="button" class="close" aria-label="Close button" aria-describedby="modal-title"
(click)="modal.dismiss()">
<button type="button" [disabled]="isPending" class="close" aria-label="Close button" aria-describedby="modal-title"
(click)="close()">
<span aria-hidden="true">&times;</span>
</button>
</div>
@@ -64,25 +64,27 @@
<small>{{ ('submission.sections.describe.relationship-lookup.selected' | translate: {size: (selection$ | async)?.length || 0}) }}</small>
<div class="buttons-container">
<div class="close-button">
<button type="button" class="btn btn-danger" (click)="close()">{{ ('submission.sections.describe.relationship-lookup.close' | translate) }}</button>
<button type="button" [disabled]="isPending" class="btn btn-outline-secondary" (click)="close()">
<span *ngIf="isPending" class="spinner-border spinner-border-sm" role="status"
aria-hidden="true"></span>
{{ ('submission.sections.describe.relationship-lookup.close' | translate) }}</button>
</div>
<div *ngIf="isEditRelationship" class="button-row bottom">
<div class="float-right">
<button class="btn btn-danger discard" *ngIf="!(isReinstatable | async)"
[disabled]="!(hasChanges | async)"
(click)="discardEv()"><i
class="fas fa-times"></i>
<button class="btn btn-danger discard"
[disabled]="(toAdd.length == 0 && toRemove.length == 0) || isPending"
(click)="discardEv()">
<span *ngIf="isPending" class="spinner-border spinner-border-sm" role="status"
aria-hidden="true"></span>
<i class="fas fa-times"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.discard-button" | translate}}</span>
</button>
<button class="btn btn-warning reinstate" *ngIf="isReinstatable | async"
(click)="reinstateEv()"><i
class="fas fa-undo-alt"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.reinstate-button" | translate}}</span>
</button>
<!-- [disabled]="!(hasChanges | async)" -->
<button class="btn btn-primary submit"
(click)="submitEv()"><i
class="fas fa-save"></i>
[disabled]="(toAdd.length == 0 && toRemove.length == 0) || isPending"
(click)="submitEv()">
<span *ngIf="isPending" class="spinner-border spinner-border-sm" role="status"
aria-hidden="true"></span>
<i class="fas fa-save"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.save-button" | translate}}</span>
</button>
</div>

View File

@@ -8,6 +8,10 @@
width: 90%;
margin: 5%;
}
.spinner-border{
vertical-align: middle;
}
}
.buttons-container{

View File

@@ -147,11 +147,8 @@ describe('DsDynamicLookupRelationModalComponent', () => {
component.submissionId = submissionId;
component.isEditRelationship = true;
component.currentItemIsLeftItem$ = observableOf(true);
component.hasChanges = observableOf(false);
component.isReinstatable = observableOf(false);
component.submit = new EventEmitter();
component.reinstate = new EventEmitter();
component.discard = new EventEmitter();
component.toAdd = [];
component.toRemove = [];
fixture.detectChanges();
});
@@ -213,14 +210,12 @@ describe('DsDynamicLookupRelationModalComponent', () => {
it('discard button should be disabled', () => {
expect(debugElement.query(By.css('.discard')).nativeElement?.disabled).toBeTrue();
});
it('should not show reinstate button', () => {
expect(debugElement.query(By.css('.reinstate'))).toBeNull();
});
});
describe('when changes happen', () => {
beforeEach(() => {
component.hasChanges = observableOf(true);
component.toAdd.push(searchResult1);
component.toRemove.push(searchResult2);
fixture.detectChanges();
});
it('submit button should be enabled', () => {
@@ -241,25 +236,18 @@ describe('DsDynamicLookupRelationModalComponent', () => {
});
});
describe('when changes happen & isReinstatable', () => {
describe('when request starts and isPending changes', () => {
beforeEach(() => {
component.hasChanges = observableOf(true);
component.isReinstatable = observableOf(true);
component.isPending = true;
fixture.detectChanges();
});
it('should show reinstate button', () => {
expect(debugElement.query(By.css('.reinstate'))).toBeTruthy();
});
it('should not show discard button', () => {
expect(debugElement.query(By.css('.discard'))).toBeNull();
});
it('reinstate button should be enabled', () => {
expect(debugElement.query(By.css('.reinstate')).nativeElement.disabled).toBeFalse();
});
it('should call reinstateEv when reinstate clicked', () => {
const reinstateFunct = spyOn((component as any), 'reinstateEv');
debugElement.query(By.css('.reinstate')).nativeElement.click();
expect(reinstateFunct).toHaveBeenCalled();
it('there should show 3 spinner for the 3 buttons', () => {
expect(debugElement.queryAll(By.css('.spinner-border')).length).toEqual(3);
});
});
});

View File

@@ -136,35 +136,11 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
*/
isEditRelationship = false;
/**
* Observable to check if any change has been made
*/
hasChanges: Observable<boolean>;
/**
* Observable to check if any discard has been made
*/
isReinstatable: Observable<boolean>;
/**
* Submit event emiter to emit to parent
*/
submit: EventEmitter<any>;
/**
* Reinstate event emiter to emit to parent
*/
reinstate: EventEmitter<any>;
/**
* Discard event emiter to emit to parent
*/
discard: EventEmitter<any>;
toAdd = [];
toRemove = [];
isPending = false;
constructor(
public modal: NgbActiveModal,
private selectableListService: SelectableListService,
@@ -213,6 +189,8 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
}
close() {
this.toAdd = [];
this.toRemove = [];
this.modal.close();
}
@@ -305,26 +283,18 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
Object.values(this.subMap).forEach((subscription) => subscription.unsubscribe());
}
/* tslint:disable:no-empty */
/**
* Called when discard button is clicked, emit discard event to parent to conclude functionality
*/
discardEv(): void {
this.discard.emit();
}
/**
* Called when submit button is clicked, emit submit event to parent to conclude functionality
*/
submitEv(): void {
console.log(this.toAdd, this.toRemove);
// this.submit.emit();
}
/**
* Called when reinstate button is clicked, emit reinstate event to parent to conclude functionality
*/
reinstateEv(): void {
this.reinstate.emit();
}
/* tslint:enable:no-empty */
}

View File

@@ -26,6 +26,12 @@ import { RelationshipType } from '../../../../../../core/shared/item-relationshi
import { Relationship } from '../../../../../../core/shared/item-relationships/relationship.model';
import { result } from 'lodash';
import {
FieldUpdate,
FieldUpdates,
RelationshipIdentifiable
} from '../../../../../../core/data/object-updates/object-updates.reducer';
@Component({
selector: 'ds-dynamic-lookup-relation-search-tab',
@@ -263,15 +269,6 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
const uuid = arrUrl[ arrUrl.length - 1 ];
return this.getRelatedItem(uuid,resultListOfItems);
// return {
// uuid: relationship.id,
// type: this.relationshipType,
// relationship,
// nameVariant,
// } as RelationshipIdentifiable;
});
if ( selectableObject.length > 0 ) {