Skip to content

Commit 3950f90

Browse files
authored
Merge branch 'DSpace:main' into feat/i18n
2 parents 3b9d401 + f7a14a1 commit 3950f90

44 files changed

Lines changed: 2718 additions & 9428 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/app/access-control/group-registry/group-form/members-list/members-list.component.html

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,33 @@ <h3>{{messagePrefix + '.headMembers' | translate}}</h3>
2020
</tr>
2121
</thead>
2222
<tbody>
23-
<tr *ngFor="let eperson of (ePeopleMembersOfGroup | async)?.page">
24-
<td class="align-middle">{{eperson.id}}</td>
23+
<tr *ngFor="let epersonDTO of (ePeopleMembersOfGroup | async)?.page">
24+
<td class="align-middle">{{epersonDTO.eperson.id}}</td>
2525
<td class="align-middle">
26-
<a [routerLink]="getEPersonEditRoute(eperson.id)">
27-
{{ dsoNameService.getName(eperson) }}
26+
<a [routerLink]="getEPersonEditRoute(epersonDTO.eperson.id)">
27+
{{ dsoNameService.getName(epersonDTO.eperson) }}
2828
</a>
2929
</td>
3030
<td class="align-middle">
31-
{{messagePrefix + '.table.email' | translate}}: {{ eperson.email ? eperson.email : '-' }}<br/>
32-
{{messagePrefix + '.table.netid' | translate}}: {{ eperson.netid ? eperson.netid : '-' }}
31+
{{messagePrefix + '.table.email' | translate}}: {{ epersonDTO.eperson.email ? epersonDTO.eperson.email : '-' }}<br/>
32+
{{messagePrefix + '.table.netid' | translate}}: {{ epersonDTO.eperson.netid ? epersonDTO.eperson.netid : '-' }}
3333
</td>
3434
<td class="align-middle">
3535
<div class="btn-group edit-field">
36-
<button (click)="deleteMemberFromGroup(eperson)"
36+
<button (click)="deleteMemberFromGroup(epersonDTO.eperson)"
37+
*ngIf="epersonDTO.ableToDelete"
3738
[disabled]="actionConfig.remove.disabled"
3839
[ngClass]="['btn btn-sm', actionConfig.remove.css]"
39-
title="{{messagePrefix + '.table.edit.buttons.remove' | translate: { name: dsoNameService.getName(eperson) } }}">
40+
title="{{messagePrefix + '.table.edit.buttons.remove' | translate: { name: dsoNameService.getName(epersonDTO.eperson) } }}">
4041
<i [ngClass]="actionConfig.remove.icon"></i>
4142
</button>
43+
<button *ngIf="!epersonDTO.ableToDelete"
44+
(click)="addMemberToGroup(epersonDTO.eperson)"
45+
[disabled]="actionConfig.add.disabled"
46+
[ngClass]="['btn btn-sm', actionConfig.add.css]"
47+
title="{{messagePrefix + '.table.edit.buttons.add' | translate: { name: dsoNameService.getName(epersonDTO.eperson) } }}">
48+
<i [ngClass]="actionConfig.add.icon"></i>
49+
</button>
4250
</div>
4351
</td>
4452
</tr>

src/app/access-control/group-registry/group-form/members-list/members-list.component.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -222,13 +222,13 @@ describe('MembersListComponent', () => {
222222

223223
describe('if first delete button is pressed', () => {
224224
beforeEach(() => {
225+
spyOn(component, 'search').and.callThrough();
225226
const deleteButton: DebugElement = fixture.debugElement.query(By.css('#ePeopleMembersOfGroup tbody .fa-trash-alt'));
226227
deleteButton.nativeElement.click();
227228
fixture.detectChanges();
228229
});
229-
it('then no ePerson remains as a member of the active group.', () => {
230-
const epersonsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tbody tr'));
231-
expect(epersonsFound.length).toEqual(0);
230+
it('should trigger the search to add the user back to the search table', () => {
231+
expect(component.search).toHaveBeenCalled();
232232
});
233233
});
234234
});
@@ -264,13 +264,13 @@ describe('MembersListComponent', () => {
264264

265265
describe('if first add button is pressed', () => {
266266
beforeEach(() => {
267+
spyOn(component, 'search').and.callThrough();
267268
const addButton: DebugElement = fixture.debugElement.query(By.css('#epersonsSearch tbody .fa-plus'));
268269
addButton.nativeElement.click();
269270
fixture.detectChanges();
270271
});
271-
it('then all (two) ePersons are member of the active group. No non-members left', () => {
272-
epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr'));
273-
expect(epersonsFound.length).toEqual(0);
272+
it('should trigger the search to remove the user from the search table', () => {
273+
expect(component.search).toHaveBeenCalled();
274274
});
275275
});
276276
});

src/app/access-control/group-registry/group-form/members-list/members-list.component.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,29 @@ import {
2424
} from '@ngx-translate/core';
2525
import {
2626
BehaviorSubject,
27+
combineLatest as observableCombineLatest,
2728
Observable,
29+
ObservedValueOf,
30+
of as observableOf,
2831
Subscription,
2932
} from 'rxjs';
3033
import {
34+
defaultIfEmpty,
3135
map,
3236
switchMap,
3337
take,
3438
} from 'rxjs/operators';
3539

3640
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
37-
import { PaginatedList } from '../../../../core/data/paginated-list.model';
41+
import {
42+
buildPaginatedList,
43+
PaginatedList,
44+
} from '../../../../core/data/paginated-list.model';
3845
import { RemoteData } from '../../../../core/data/remote-data';
3946
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
4047
import { GroupDataService } from '../../../../core/eperson/group-data.service';
4148
import { EPerson } from '../../../../core/eperson/models/eperson.model';
49+
import { EpersonDtoModel } from '../../../../core/eperson/models/eperson-dto.model';
4250
import { Group } from '../../../../core/eperson/models/group.model';
4351
import { PaginationService } from '../../../../core/pagination/pagination.service';
4452
import {
@@ -137,7 +145,7 @@ export class MembersListComponent implements OnInit, OnDestroy {
137145
/**
138146
* List of EPeople members of currently active group being edited
139147
*/
140-
ePeopleMembersOfGroup: BehaviorSubject<PaginatedList<EPerson>> = new BehaviorSubject<PaginatedList<EPerson>>(undefined);
148+
ePeopleMembersOfGroup: BehaviorSubject<PaginatedList<EpersonDtoModel>> = new BehaviorSubject(undefined);
141149

142150
/**
143151
* Pagination config used to display the list of EPeople that are result of EPeople search
@@ -226,10 +234,35 @@ export class MembersListComponent implements OnInit, OnDestroy {
226234
return rd;
227235
}
228236
}),
229-
getRemoteDataPayload())
230-
.subscribe((paginatedListOfEPersons: PaginatedList<EPerson>) => {
231-
this.ePeopleMembersOfGroup.next(paginatedListOfEPersons);
232-
}));
237+
switchMap((epersonListRD: RemoteData<PaginatedList<EPerson>>) => {
238+
const dtos$ = observableCombineLatest([...epersonListRD.payload.page.map((member: EPerson) => {
239+
const dto$: Observable<EpersonDtoModel> = observableCombineLatest(
240+
this.isMemberOfGroup(member), (isMember: ObservedValueOf<Observable<boolean>>) => {
241+
const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel();
242+
epersonDtoModel.eperson = member;
243+
epersonDtoModel.ableToDelete = isMember;
244+
return epersonDtoModel;
245+
});
246+
return dto$;
247+
})]);
248+
return dtos$.pipe(defaultIfEmpty([]), map((dtos: EpersonDtoModel[]) => {
249+
return buildPaginatedList(epersonListRD.payload.pageInfo, dtos);
250+
}));
251+
}),
252+
).subscribe((paginatedListOfDTOs: PaginatedList<EpersonDtoModel>) => {
253+
this.ePeopleMembersOfGroup.next(paginatedListOfDTOs);
254+
}),
255+
);
256+
}
257+
258+
/**
259+
* We always return true since this is only used by the top section (which represents all the users part of the group
260+
* in {@link MembersListComponent})
261+
*
262+
* @param possibleMember EPerson that is a possible member (being tested) of the group currently being edited
263+
*/
264+
isMemberOfGroup(possibleMember: EPerson): Observable<boolean> {
265+
return observableOf(true);
233266
}
234267

235268
/**

src/app/core/data/relationship-data.service.spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ describe('RelationshipDataService', () => {
128128
const itemService = jasmine.createSpyObj('itemService', {
129129
findById: (uuid) => createSuccessfulRemoteDataObject(relatedItems.find((relatedItem) => relatedItem.id === uuid)),
130130
findByHref: createSuccessfulRemoteDataObject$(relatedItems[0]),
131+
getIDHrefObs: (uuid: string) => observableOf(`https://demo.dspace.org/server/api/core/items/${uuid}`),
131132
});
132133

133134
const getRequestEntry$ = (successful: boolean) => {
@@ -244,6 +245,16 @@ describe('RelationshipDataService', () => {
244245
});
245246
});
246247

248+
describe('searchByItemsAndType', () => {
249+
it('should call addDependency for each item to invalidate the request when one of the items is update', () => {
250+
spyOn(service as any, 'addDependency');
251+
252+
service.searchByItemsAndType(relationshipType.id, item.id, relationshipType.leftwardType, ['item-id-1', 'item-id-2']);
253+
254+
expect((service as any).addDependency).toHaveBeenCalledTimes(2);
255+
});
256+
});
257+
247258
describe('resolveMetadataRepresentation', () => {
248259
const parentItem: Item = Object.assign(new Item(), {
249260
id: 'parent-item',

src/app/core/data/relationship-data.service.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -574,13 +574,18 @@ export class RelationshipDataService extends IdentifiableDataService<Relationshi
574574
);
575575
});
576576

577-
return this.searchBy(
577+
const searchRD$: Observable<RemoteData<PaginatedList<Relationship>>> = this.searchBy(
578578
'byItemsAndType',
579579
{
580580
searchParams: searchParams,
581581
},
582582
) as Observable<RemoteData<PaginatedList<Relationship>>>;
583583

584+
arrayOfItemIds.forEach((itemId: string) => {
585+
this.addDependency(searchRD$, this.itemService.getIDHrefObs(encodeURIComponent(itemId)));
586+
});
587+
588+
return searchRD$;
584589
}
585590

586591
/**

src/app/core/eperson/eperson-data.service.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,17 @@ export class EPersonDataService extends IdentifiableDataService<EPerson> impleme
107107
* @param scope Scope of the EPeople search, default byMetadata
108108
* @param query Query of search
109109
* @param options Options of search request
110+
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
111+
* no valid cached version. Defaults to true
112+
* @param reRequestOnStale Whether or not the request should automatically be re-
113+
* requested after the response becomes stale
110114
*/
111-
public searchByScope(scope: string, query: string, options: FindListOptions = {}, useCachedVersionIfAvailable?: boolean): Observable<RemoteData<PaginatedList<EPerson>>> {
115+
public searchByScope(scope: string, query: string, options: FindListOptions = {}, useCachedVersionIfAvailable?: boolean, reRequestOnStale = true): Observable<RemoteData<PaginatedList<EPerson>>> {
112116
switch (scope) {
113117
case 'metadata':
114-
return this.getEpeopleByMetadata(query.trim(), options, useCachedVersionIfAvailable);
118+
return this.getEpeopleByMetadata(query.trim(), options, useCachedVersionIfAvailable, reRequestOnStale);
115119
case 'email':
116-
return this.getEPersonByEmail(query.trim()).pipe(
120+
return this.getEPersonByEmail(query.trim(), useCachedVersionIfAvailable, reRequestOnStale).pipe(
117121
map((rd: RemoteData<EPerson | NoContent>) => {
118122
if (rd.hasSucceeded) {
119123
// Turn the single EPerson or NoContent in to a PaginatedList<EPerson>
@@ -145,7 +149,7 @@ export class EPersonDataService extends IdentifiableDataService<EPerson> impleme
145149
}),
146150
);
147151
default:
148-
return this.getEpeopleByMetadata(query.trim(), options, useCachedVersionIfAvailable);
152+
return this.getEpeopleByMetadata(query.trim(), options, useCachedVersionIfAvailable, reRequestOnStale);
149153
}
150154
}
151155

src/app/item-page/edit-item-page/item-relationships/edit-item-relationships.service.spec.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ describe('EditItemRelationshipsService', () => {
185185

186186
expect(itemService.invalidateByHref).toHaveBeenCalledWith(currentItem.self);
187187
expect(itemService.invalidateByHref).toHaveBeenCalledWith(relationshipItem1.self);
188+
expect(itemService.invalidateByHref).toHaveBeenCalledWith(relationshipItem2.self);
188189

189190
expect(notificationsService.success).toHaveBeenCalledTimes(1);
190191
});
@@ -265,6 +266,116 @@ describe('EditItemRelationshipsService', () => {
265266
});
266267
});
267268

269+
describe('isProvidedItemTypeLeftType', () => {
270+
it('should return true if the provided item corresponds to the left type of the relationship', (done) => {
271+
const relationshipType = Object.assign(new RelationshipType(), {
272+
leftType: createSuccessfulRemoteDataObject$({ id: 'leftType' }),
273+
rightType: createSuccessfulRemoteDataObject$({ id: 'rightType' }),
274+
});
275+
const itemType = Object.assign(new ItemType(), { id: 'leftType' } );
276+
const item = Object.assign(new Item(), { uuid: 'item-uuid' });
277+
278+
const result = service.isProvidedItemTypeLeftType(relationshipType, itemType, item);
279+
result.subscribe((resultValue) => {
280+
expect(resultValue).toBeTrue();
281+
done();
282+
});
283+
});
284+
285+
it('should return false if the provided item corresponds to the right type of the relationship', (done) => {
286+
const relationshipType = Object.assign(new RelationshipType(), {
287+
leftType: createSuccessfulRemoteDataObject$({ id: 'leftType' }),
288+
rightType: createSuccessfulRemoteDataObject$({ id: 'rightType' }),
289+
});
290+
const itemType = Object.assign(new ItemType(), { id: 'rightType' } );
291+
const item = Object.assign(new Item(), { uuid: 'item-uuid' });
292+
293+
const result = service.isProvidedItemTypeLeftType(relationshipType, itemType, item);
294+
result.subscribe((resultValue) => {
295+
expect(resultValue).toBeFalse();
296+
done();
297+
});
298+
});
299+
300+
it('should return undefined if the provided item corresponds does not match any of the relationship types', (done) => {
301+
const relationshipType = Object.assign(new RelationshipType(), {
302+
leftType: createSuccessfulRemoteDataObject$({ id: 'leftType' }),
303+
rightType: createSuccessfulRemoteDataObject$({ id: 'rightType' }),
304+
});
305+
const itemType = Object.assign(new ItemType(), { id: 'something-else' } );
306+
const item = Object.assign(new Item(), { uuid: 'item-uuid' });
307+
308+
const result = service.isProvidedItemTypeLeftType(relationshipType, itemType, item);
309+
result.subscribe((resultValue) => {
310+
expect(resultValue).toBeUndefined();
311+
done();
312+
});
313+
});
314+
});
315+
316+
describe('relationshipMatchesBothSameTypes', () => {
317+
it('should return true if both left and right type of the relationship type are the same and match the provided itemtype', (done) => {
318+
const relationshipType = Object.assign(new RelationshipType(), {
319+
leftType: createSuccessfulRemoteDataObject$({ id: 'sameType' }),
320+
rightType: createSuccessfulRemoteDataObject$({ id:'sameType' }),
321+
leftwardType: 'isDepartmentOfDivision',
322+
rightwardType: 'isDivisionOfDepartment',
323+
});
324+
const itemType = Object.assign(new ItemType(), { id: 'sameType' } );
325+
326+
const result = service.shouldDisplayBothRelationshipSides(relationshipType, itemType);
327+
result.subscribe((resultValue) => {
328+
expect(resultValue).toBeTrue();
329+
done();
330+
});
331+
});
332+
it('should return false if both left and right type of the relationship type are the same and match the provided itemtype but the leftwardType & rightwardType is identical', (done) => {
333+
const relationshipType = Object.assign(new RelationshipType(), {
334+
leftType: createSuccessfulRemoteDataObject$({ id: 'sameType' }),
335+
rightType: createSuccessfulRemoteDataObject$({ id: 'sameType' }),
336+
leftwardType: 'isOrgUnitOfOrgUnit',
337+
rightwardType: 'isOrgUnitOfOrgUnit',
338+
});
339+
const itemType = Object.assign(new ItemType(), { id: 'sameType' });
340+
341+
const result = service.shouldDisplayBothRelationshipSides(relationshipType, itemType);
342+
result.subscribe((resultValue) => {
343+
expect(resultValue).toBeFalse();
344+
done();
345+
});
346+
});
347+
it('should return false if both left and right type of the relationship type are the same and do not match the provided itemtype', (done) => {
348+
const relationshipType = Object.assign(new RelationshipType(), {
349+
leftType: createSuccessfulRemoteDataObject$({ id: 'sameType' }),
350+
rightType: createSuccessfulRemoteDataObject$({ id: 'sameType' }),
351+
leftwardType: 'isDepartmentOfDivision',
352+
rightwardType: 'isDivisionOfDepartment',
353+
});
354+
const itemType = Object.assign(new ItemType(), { id: 'something-else' } );
355+
356+
const result = service.shouldDisplayBothRelationshipSides(relationshipType, itemType);
357+
result.subscribe((resultValue) => {
358+
expect(resultValue).toBeFalse();
359+
done();
360+
});
361+
});
362+
it('should return false if both left and right type of the relationship type are different', (done) => {
363+
const relationshipType = Object.assign(new RelationshipType(), {
364+
leftType: createSuccessfulRemoteDataObject$({ id: 'leftType' }),
365+
rightType: createSuccessfulRemoteDataObject$({ id: 'rightType' }),
366+
leftwardType: 'isAuthorOfPublication',
367+
rightwardType: 'isPublicationOfAuthor',
368+
});
369+
const itemType = Object.assign(new ItemType(), { id: 'leftType' } );
370+
371+
const result = service.shouldDisplayBothRelationshipSides(relationshipType, itemType);
372+
result.subscribe((resultValue) => {
373+
expect(resultValue).toBeFalse();
374+
done();
375+
});
376+
});
377+
});
378+
268379
describe('displayNotifications', () => {
269380
it('should show one success notification when multiple requests succeeded', () => {
270381
service.displayNotifications([

0 commit comments

Comments
 (0)