mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge pull request #2627 from DSpace/backport-2545-to-dspace-7_x
[Port dspace-7_x] Fix "Edit Group" page always requests all member Subgroups & EPersons
This commit is contained in:
@@ -230,12 +230,14 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
|||||||
this.groupBeingEdited = activeGroup;
|
this.groupBeingEdited = activeGroup;
|
||||||
|
|
||||||
if (linkedObject?.name) {
|
if (linkedObject?.name) {
|
||||||
this.formBuilderService.insertFormGroupControl(1, this.formGroup, this.formModel, this.groupCommunity);
|
if (!this.formGroup.controls.groupCommunity) {
|
||||||
this.formGroup.patchValue({
|
this.formBuilderService.insertFormGroupControl(1, this.formGroup, this.formModel, this.groupCommunity);
|
||||||
groupName: activeGroup.name,
|
this.formGroup.patchValue({
|
||||||
groupCommunity: linkedObject?.name ?? '',
|
groupName: activeGroup.name,
|
||||||
groupDescription: activeGroup.firstMetadataValue('dc.description'),
|
groupCommunity: linkedObject?.name ?? '',
|
||||||
});
|
groupDescription: activeGroup.firstMetadataValue('dc.description'),
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.formModel = [
|
this.formModel = [
|
||||||
this.groupName,
|
this.groupName,
|
||||||
|
@@ -1,6 +1,60 @@
|
|||||||
<ng-container>
|
<ng-container>
|
||||||
<h3 class="border-bottom pb-2">{{messagePrefix + '.head' | translate}}</h3>
|
<h3 class="border-bottom pb-2">{{messagePrefix + '.head' | translate}}</h3>
|
||||||
|
|
||||||
|
<h4>{{messagePrefix + '.headMembers' | translate}}</h4>
|
||||||
|
|
||||||
|
<ds-pagination *ngIf="(ePeopleMembersOfGroup | async)?.totalElements > 0"
|
||||||
|
[paginationOptions]="config"
|
||||||
|
[pageInfoState]="(ePeopleMembersOfGroup | async)"
|
||||||
|
[collectionSize]="(ePeopleMembersOfGroup | async)?.totalElements"
|
||||||
|
[hideGear]="true"
|
||||||
|
[hidePagerWhenSinglePage]="true">
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="ePeopleMembersOfGroup" class="table table-striped table-hover table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="align-middle">{{messagePrefix + '.table.id' | translate}}</th>
|
||||||
|
<th scope="col" class="align-middle">{{messagePrefix + '.table.name' | translate}}</th>
|
||||||
|
<th scope="col" class="align-middle">{{messagePrefix + '.table.identity' | translate}}</th>
|
||||||
|
<th class="align-middle">{{messagePrefix + '.table.edit' | translate}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let eperson of (ePeopleMembersOfGroup | async)?.page">
|
||||||
|
<td class="align-middle">{{eperson.id}}</td>
|
||||||
|
<td class="align-middle">
|
||||||
|
<a (click)="ePersonDataService.startEditingNewEPerson(eperson)"
|
||||||
|
[routerLink]="[ePersonDataService.getEPeoplePageRouterLink()]">
|
||||||
|
{{ dsoNameService.getName(eperson) }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td class="align-middle">
|
||||||
|
{{messagePrefix + '.table.email' | translate}}: {{ eperson.email ? eperson.email : '-' }}<br/>
|
||||||
|
{{messagePrefix + '.table.netid' | translate}}: {{ eperson.netid ? eperson.netid : '-' }}
|
||||||
|
</td>
|
||||||
|
<td class="align-middle">
|
||||||
|
<div class="btn-group edit-field">
|
||||||
|
<button (click)="deleteMemberFromGroup(eperson)"
|
||||||
|
[disabled]="actionConfig.remove.disabled"
|
||||||
|
[ngClass]="['btn btn-sm', actionConfig.remove.css]"
|
||||||
|
title="{{messagePrefix + '.table.edit.buttons.remove' | translate: { name: dsoNameService.getName(eperson) } }}">
|
||||||
|
<i [ngClass]="actionConfig.remove.icon"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</ds-pagination>
|
||||||
|
|
||||||
|
<div *ngIf="(ePeopleMembersOfGroup | async) == undefined || (ePeopleMembersOfGroup | async)?.totalElements == 0" class="alert alert-info w-100 mb-2"
|
||||||
|
role="alert">
|
||||||
|
{{messagePrefix + '.no-members-yet' | translate}}
|
||||||
|
</div>
|
||||||
|
|
||||||
<h4 id="search" class="border-bottom pb-2">
|
<h4 id="search" class="border-bottom pb-2">
|
||||||
<span
|
<span
|
||||||
*dsContextHelp="{
|
*dsContextHelp="{
|
||||||
@@ -15,14 +69,8 @@
|
|||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
|
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
|
||||||
<div>
|
<div class="flex-grow-1 mr-3">
|
||||||
<select name="scope" id="scope" formControlName="scope" class="form-control" aria-label="Search scope">
|
<div class="form-group input-group mr-3">
|
||||||
<option value="metadata">{{messagePrefix + '.search.scope.metadata' | translate}}</option>
|
|
||||||
<option value="email">{{messagePrefix + '.search.scope.email' | translate}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="flex-grow-1 mr-3 ml-3">
|
|
||||||
<div class="form-group input-group">
|
|
||||||
<input type="text" name="query" id="query" formControlName="query"
|
<input type="text" name="query" id="query" formControlName="query"
|
||||||
class="form-control" aria-label="Search input">
|
class="form-control" aria-label="Search input">
|
||||||
<span class="input-group-append">
|
<span class="input-group-append">
|
||||||
@@ -37,10 +85,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<ds-pagination *ngIf="(ePeopleSearchDtos | async)?.totalElements > 0"
|
<ds-pagination *ngIf="(ePeopleSearch | async)?.totalElements > 0"
|
||||||
[paginationOptions]="configSearch"
|
[paginationOptions]="configSearch"
|
||||||
[pageInfoState]="(ePeopleSearchDtos | async)"
|
[pageInfoState]="(ePeopleSearch | async)"
|
||||||
[collectionSize]="(ePeopleSearchDtos | async)?.totalElements"
|
[collectionSize]="(ePeopleSearch | async)?.totalElements"
|
||||||
[hideGear]="true"
|
[hideGear]="true"
|
||||||
[hidePagerWhenSinglePage]="true">
|
[hidePagerWhenSinglePage]="true">
|
||||||
|
|
||||||
@@ -55,33 +103,24 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let ePerson of (ePeopleSearchDtos | async)?.page">
|
<tr *ngFor="let eperson of (ePeopleSearch | async)?.page">
|
||||||
<td class="align-middle">{{ePerson.eperson.id}}</td>
|
<td class="align-middle">{{eperson.id}}</td>
|
||||||
<td class="align-middle">
|
<td class="align-middle">
|
||||||
<a (click)="ePersonDataService.startEditingNewEPerson(ePerson.eperson)"
|
<a (click)="ePersonDataService.startEditingNewEPerson(eperson)"
|
||||||
[routerLink]="[ePersonDataService.getEPeoplePageRouterLink()]">
|
[routerLink]="[ePersonDataService.getEPeoplePageRouterLink()]">
|
||||||
{{ dsoNameService.getName(ePerson.eperson) }}
|
{{ dsoNameService.getName(eperson) }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle">
|
<td class="align-middle">
|
||||||
{{messagePrefix + '.table.email' | translate}}: {{ ePerson.eperson.email ? ePerson.eperson.email : '-' }}<br/>
|
{{messagePrefix + '.table.email' | translate}}: {{ eperson.email ? eperson.email : '-' }}<br/>
|
||||||
{{messagePrefix + '.table.netid' | translate}}: {{ ePerson.eperson.netid ? ePerson.eperson.netid : '-' }}
|
{{messagePrefix + '.table.netid' | translate}}: {{ eperson.netid ? eperson.netid : '-' }}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle">
|
<td class="align-middle">
|
||||||
<div class="btn-group edit-field">
|
<div class="btn-group edit-field">
|
||||||
<button *ngIf="ePerson.memberOfGroup"
|
<button (click)="addMemberToGroup(eperson)"
|
||||||
(click)="deleteMemberFromGroup(ePerson)"
|
|
||||||
[disabled]="actionConfig.remove.disabled"
|
|
||||||
[ngClass]="['btn btn-sm', actionConfig.remove.css]"
|
|
||||||
title="{{messagePrefix + '.table.edit.buttons.remove' | translate: { name: dsoNameService.getName(ePerson.eperson) } }}">
|
|
||||||
<i [ngClass]="actionConfig.remove.icon"></i>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button *ngIf="!ePerson.memberOfGroup"
|
|
||||||
(click)="addMemberToGroup(ePerson)"
|
|
||||||
[disabled]="actionConfig.add.disabled"
|
[disabled]="actionConfig.add.disabled"
|
||||||
[ngClass]="['btn btn-sm', actionConfig.add.css]"
|
[ngClass]="['btn btn-sm', actionConfig.add.css]"
|
||||||
title="{{messagePrefix + '.table.edit.buttons.add' | translate: { name: dsoNameService.getName(ePerson.eperson) } }}">
|
title="{{messagePrefix + '.table.edit.buttons.add' | translate: { name: dsoNameService.getName(eperson) } }}">
|
||||||
<i [ngClass]="actionConfig.add.icon"></i>
|
<i [ngClass]="actionConfig.add.icon"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -93,72 +132,10 @@
|
|||||||
|
|
||||||
</ds-pagination>
|
</ds-pagination>
|
||||||
|
|
||||||
<div *ngIf="(ePeopleSearchDtos | async)?.totalElements == 0 && searchDone"
|
<div *ngIf="(ePeopleSearch | async)?.totalElements == 0 && searchDone"
|
||||||
class="alert alert-info w-100 mb-2"
|
class="alert alert-info w-100 mb-2"
|
||||||
role="alert">
|
role="alert">
|
||||||
{{messagePrefix + '.no-items' | translate}}
|
{{messagePrefix + '.no-items' | translate}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h4>{{messagePrefix + '.headMembers' | translate}}</h4>
|
|
||||||
|
|
||||||
<ds-pagination *ngIf="(ePeopleMembersOfGroupDtos | async)?.totalElements > 0"
|
|
||||||
[paginationOptions]="config"
|
|
||||||
[pageInfoState]="(ePeopleMembersOfGroupDtos | async)"
|
|
||||||
[collectionSize]="(ePeopleMembersOfGroupDtos | async)?.totalElements"
|
|
||||||
[hideGear]="true"
|
|
||||||
[hidePagerWhenSinglePage]="true">
|
|
||||||
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table id="ePeopleMembersOfGroup" class="table table-striped table-hover table-bordered">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.id' | translate}}</th>
|
|
||||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.name' | translate}}</th>
|
|
||||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.identity' | translate}}</th>
|
|
||||||
<th class="align-middle">{{messagePrefix + '.table.edit' | translate}}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr *ngFor="let ePerson of (ePeopleMembersOfGroupDtos | async)?.page">
|
|
||||||
<td class="align-middle">{{ePerson.eperson.id}}</td>
|
|
||||||
<td class="align-middle">
|
|
||||||
<a (click)="ePersonDataService.startEditingNewEPerson(ePerson.eperson)"
|
|
||||||
[routerLink]="[ePersonDataService.getEPeoplePageRouterLink()]">
|
|
||||||
{{ dsoNameService.getName(ePerson.eperson) }}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td class="align-middle">
|
|
||||||
{{messagePrefix + '.table.email' | translate}}: {{ ePerson.eperson.email ? ePerson.eperson.email : '-' }}<br/>
|
|
||||||
{{messagePrefix + '.table.netid' | translate}}: {{ ePerson.eperson.netid ? ePerson.eperson.netid : '-' }}
|
|
||||||
</td>
|
|
||||||
<td class="align-middle">
|
|
||||||
<div class="btn-group edit-field">
|
|
||||||
<button *ngIf="ePerson.memberOfGroup"
|
|
||||||
(click)="deleteMemberFromGroup(ePerson)"
|
|
||||||
[disabled]="actionConfig.remove.disabled"
|
|
||||||
[ngClass]="['btn btn-sm', actionConfig.remove.css]"
|
|
||||||
title="{{messagePrefix + '.table.edit.buttons.remove' | translate: { name: dsoNameService.getName(ePerson.eperson) } }}">
|
|
||||||
<i [ngClass]="actionConfig.remove.icon"></i>
|
|
||||||
</button>
|
|
||||||
<button *ngIf="!ePerson.memberOfGroup"
|
|
||||||
(click)="addMemberToGroup(ePerson)"
|
|
||||||
[disabled]="actionConfig.add.disabled"
|
|
||||||
[ngClass]="['btn btn-sm', actionConfig.add.css]"
|
|
||||||
title="{{messagePrefix + '.table.edit.buttons.add' | translate: { name: dsoNameService.getName(ePerson.eperson) } }}">
|
|
||||||
<i [ngClass]="actionConfig.add.icon"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</ds-pagination>
|
|
||||||
|
|
||||||
<div *ngIf="(ePeopleMembersOfGroupDtos | async) == undefined || (ePeopleMembersOfGroupDtos | async)?.totalElements == 0" class="alert alert-info w-100 mb-2"
|
|
||||||
role="alert">
|
|
||||||
{{messagePrefix + '.no-members-yet' | translate}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@@ -17,7 +17,7 @@ import { Group } from '../../../../core/eperson/models/group.model';
|
|||||||
import { PageInfo } from '../../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||||
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
|
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
|
||||||
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||||
import { GroupMock, GroupMock2 } from '../../../../shared/testing/group-mock';
|
import { GroupMock } from '../../../../shared/testing/group-mock';
|
||||||
import { MembersListComponent } from './members-list.component';
|
import { MembersListComponent } from './members-list.component';
|
||||||
import { EPersonMock, EPersonMock2 } from '../../../../shared/testing/eperson.mock';
|
import { EPersonMock, EPersonMock2 } from '../../../../shared/testing/eperson.mock';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
|
||||||
@@ -39,28 +39,26 @@ describe('MembersListComponent', () => {
|
|||||||
let ePersonDataServiceStub: any;
|
let ePersonDataServiceStub: any;
|
||||||
let groupsDataServiceStub: any;
|
let groupsDataServiceStub: any;
|
||||||
let activeGroup;
|
let activeGroup;
|
||||||
let allEPersons: EPerson[];
|
|
||||||
let allGroups: Group[];
|
|
||||||
let epersonMembers: EPerson[];
|
let epersonMembers: EPerson[];
|
||||||
let subgroupMembers: Group[];
|
let epersonNonMembers: EPerson[];
|
||||||
let paginationService;
|
let paginationService;
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
activeGroup = GroupMock;
|
activeGroup = GroupMock;
|
||||||
epersonMembers = [EPersonMock2];
|
epersonMembers = [EPersonMock2];
|
||||||
subgroupMembers = [GroupMock2];
|
epersonNonMembers = [EPersonMock];
|
||||||
allEPersons = [EPersonMock, EPersonMock2];
|
|
||||||
allGroups = [GroupMock, GroupMock2];
|
|
||||||
ePersonDataServiceStub = {
|
ePersonDataServiceStub = {
|
||||||
activeGroup: activeGroup,
|
activeGroup: activeGroup,
|
||||||
epersonMembers: epersonMembers,
|
epersonMembers: epersonMembers,
|
||||||
subgroupMembers: subgroupMembers,
|
epersonNonMembers: epersonNonMembers,
|
||||||
|
// This method is used to get all the current members
|
||||||
findListByHref(_href: string): Observable<RemoteData<PaginatedList<EPerson>>> {
|
findListByHref(_href: string): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
return createSuccessfulRemoteDataObject$(buildPaginatedList<EPerson>(new PageInfo(), groupsDataServiceStub.getEPersonMembers()));
|
return createSuccessfulRemoteDataObject$(buildPaginatedList<EPerson>(new PageInfo(), groupsDataServiceStub.getEPersonMembers()));
|
||||||
},
|
},
|
||||||
searchByScope(scope: string, query: string): Observable<RemoteData<PaginatedList<EPerson>>> {
|
// This method is used to search across *non-members*
|
||||||
|
searchNonMembers(query: string, group: string): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
if (query === '') {
|
if (query === '') {
|
||||||
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), allEPersons));
|
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), epersonNonMembers));
|
||||||
}
|
}
|
||||||
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []));
|
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []));
|
||||||
},
|
},
|
||||||
@@ -77,22 +75,22 @@ describe('MembersListComponent', () => {
|
|||||||
groupsDataServiceStub = {
|
groupsDataServiceStub = {
|
||||||
activeGroup: activeGroup,
|
activeGroup: activeGroup,
|
||||||
epersonMembers: epersonMembers,
|
epersonMembers: epersonMembers,
|
||||||
subgroupMembers: subgroupMembers,
|
epersonNonMembers: epersonNonMembers,
|
||||||
allGroups: allGroups,
|
|
||||||
getActiveGroup(): Observable<Group> {
|
getActiveGroup(): Observable<Group> {
|
||||||
return observableOf(activeGroup);
|
return observableOf(activeGroup);
|
||||||
},
|
},
|
||||||
getEPersonMembers() {
|
getEPersonMembers() {
|
||||||
return this.epersonMembers;
|
return this.epersonMembers;
|
||||||
},
|
},
|
||||||
searchGroups(query: string): Observable<RemoteData<PaginatedList<Group>>> {
|
addMemberToGroup(parentGroup, epersonToAdd: EPerson): Observable<RestResponse> {
|
||||||
if (query === '') {
|
// Add eperson to list of members
|
||||||
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), this.allGroups));
|
this.epersonMembers = [...this.epersonMembers, epersonToAdd];
|
||||||
}
|
// Remove eperson from list of non-members
|
||||||
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []));
|
this.epersonNonMembers.forEach( (eperson: EPerson, index: number) => {
|
||||||
},
|
if (eperson.id === epersonToAdd.id) {
|
||||||
addMemberToGroup(parentGroup, eperson: EPerson): Observable<RestResponse> {
|
this.epersonNonMembers.splice(index, 1);
|
||||||
this.epersonMembers = [...this.epersonMembers, eperson];
|
}
|
||||||
|
});
|
||||||
return observableOf(new RestResponse(true, 200, 'Success'));
|
return observableOf(new RestResponse(true, 200, 'Success'));
|
||||||
},
|
},
|
||||||
clearGroupsRequests() {
|
clearGroupsRequests() {
|
||||||
@@ -105,14 +103,14 @@ describe('MembersListComponent', () => {
|
|||||||
return '/access-control/groups/' + group.id;
|
return '/access-control/groups/' + group.id;
|
||||||
},
|
},
|
||||||
deleteMemberFromGroup(parentGroup, epersonToDelete: EPerson): Observable<RestResponse> {
|
deleteMemberFromGroup(parentGroup, epersonToDelete: EPerson): Observable<RestResponse> {
|
||||||
this.epersonMembers = this.epersonMembers.find((eperson: EPerson) => {
|
// Remove eperson from list of members
|
||||||
if (eperson.id !== epersonToDelete.id) {
|
this.epersonMembers.forEach( (eperson: EPerson, index: number) => {
|
||||||
return eperson;
|
if (eperson.id === epersonToDelete.id) {
|
||||||
|
this.epersonMembers.splice(index, 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (this.epersonMembers === undefined) {
|
// Add eperson to list of non-members
|
||||||
this.epersonMembers = [];
|
this.epersonNonMembers = [...this.epersonNonMembers, epersonToDelete];
|
||||||
}
|
|
||||||
return observableOf(new RestResponse(true, 200, 'Success'));
|
return observableOf(new RestResponse(true, 200, 'Success'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -160,13 +158,37 @@ describe('MembersListComponent', () => {
|
|||||||
expect(comp).toBeDefined();
|
expect(comp).toBeDefined();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should show list of eperson members of current active group', () => {
|
describe('current members list', () => {
|
||||||
const epersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child'));
|
it('should show list of eperson members of current active group', () => {
|
||||||
expect(epersonIdsFound.length).toEqual(1);
|
const epersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child'));
|
||||||
epersonMembers.map((eperson: EPerson) => {
|
expect(epersonIdsFound.length).toEqual(1);
|
||||||
expect(epersonIdsFound.find((foundEl) => {
|
epersonMembers.map((eperson: EPerson) => {
|
||||||
return (foundEl.nativeElement.textContent.trim() === eperson.uuid);
|
expect(epersonIdsFound.find((foundEl) => {
|
||||||
})).toBeTruthy();
|
return (foundEl.nativeElement.textContent.trim() === eperson.uuid);
|
||||||
|
})).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show a delete button next to each member', () => {
|
||||||
|
const epersonsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tbody tr'));
|
||||||
|
epersonsFound.map((foundEPersonRowElement: DebugElement) => {
|
||||||
|
const addButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-plus'));
|
||||||
|
const deleteButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-trash-alt'));
|
||||||
|
expect(addButton).toBeNull();
|
||||||
|
expect(deleteButton).not.toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('if first delete button is pressed', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const deleteButton: DebugElement = fixture.debugElement.query(By.css('#ePeopleMembersOfGroup tbody .fa-trash-alt'));
|
||||||
|
deleteButton.nativeElement.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
it('then no ePerson remains as a member of the active group.', () => {
|
||||||
|
const epersonsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tbody tr'));
|
||||||
|
expect(epersonsFound.length).toEqual(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -174,76 +196,40 @@ describe('MembersListComponent', () => {
|
|||||||
describe('when searching without query', () => {
|
describe('when searching without query', () => {
|
||||||
let epersonsFound: DebugElement[];
|
let epersonsFound: DebugElement[];
|
||||||
beforeEach(fakeAsync(() => {
|
beforeEach(fakeAsync(() => {
|
||||||
spyOn(component, 'isMemberOfGroup').and.callFake((ePerson: EPerson) => {
|
|
||||||
return observableOf(activeGroup.epersons.includes(ePerson));
|
|
||||||
});
|
|
||||||
component.search({ scope: 'metadata', query: '' });
|
component.search({ scope: 'metadata', query: '' });
|
||||||
tick();
|
tick();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr'));
|
epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr'));
|
||||||
// Stop using the fake spy function (because otherwise the clicking on the buttons will not change anything
|
|
||||||
// because they don't change the value of activeGroup.epersons)
|
|
||||||
jasmine.getEnv().allowRespy(true);
|
|
||||||
spyOn(component, 'isMemberOfGroup').and.callThrough();
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should display all epersons', () => {
|
it('should display only non-members of the group', () => {
|
||||||
expect(epersonsFound.length).toEqual(2);
|
const epersonIdsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr td:first-child'));
|
||||||
|
expect(epersonIdsFound.length).toEqual(1);
|
||||||
|
epersonNonMembers.map((eperson: EPerson) => {
|
||||||
|
expect(epersonIdsFound.find((foundEl) => {
|
||||||
|
return (foundEl.nativeElement.textContent.trim() === eperson.uuid);
|
||||||
|
})).toBeTruthy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('if eperson is already a eperson', () => {
|
it('should display an add button next to non-members, not a delete button', () => {
|
||||||
it('should have delete button, else it should have add button', () => {
|
epersonsFound.map((foundEPersonRowElement: DebugElement) => {
|
||||||
const memberIds: string[] = activeGroup.epersons.map((ePerson: EPerson) => ePerson.id);
|
const addButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-plus'));
|
||||||
epersonsFound.map((foundEPersonRowElement: DebugElement) => {
|
const deleteButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-trash-alt'));
|
||||||
const epersonId: DebugElement = foundEPersonRowElement.query(By.css('td:first-child'));
|
expect(addButton).not.toBeNull();
|
||||||
const addButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-plus'));
|
expect(deleteButton).toBeNull();
|
||||||
const deleteButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-trash-alt'));
|
|
||||||
if (memberIds.includes(epersonId.nativeElement.textContent)) {
|
|
||||||
expect(addButton).toBeNull();
|
|
||||||
expect(deleteButton).not.toBeNull();
|
|
||||||
} else {
|
|
||||||
expect(deleteButton).toBeNull();
|
|
||||||
expect(addButton).not.toBeNull();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('if first add button is pressed', () => {
|
describe('if first add button is pressed', () => {
|
||||||
beforeEach(fakeAsync(() => {
|
beforeEach(() => {
|
||||||
const addButton: DebugElement = fixture.debugElement.query(By.css('#epersonsSearch tbody .fa-plus'));
|
const addButton: DebugElement = fixture.debugElement.query(By.css('#epersonsSearch tbody .fa-plus'));
|
||||||
addButton.nativeElement.click();
|
addButton.nativeElement.click();
|
||||||
tick();
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
}));
|
|
||||||
it('then all the ePersons are member of the active group', () => {
|
|
||||||
epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr'));
|
|
||||||
expect(epersonsFound.length).toEqual(2);
|
|
||||||
epersonsFound.map((foundEPersonRowElement: DebugElement) => {
|
|
||||||
const addButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-plus'));
|
|
||||||
const deleteButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-trash-alt'));
|
|
||||||
expect(addButton).toBeNull();
|
|
||||||
expect(deleteButton).not.toBeNull();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
it('then all (two) ePersons are member of the active group. No non-members left', () => {
|
||||||
|
|
||||||
describe('if first delete button is pressed', () => {
|
|
||||||
beforeEach(fakeAsync(() => {
|
|
||||||
const deleteButton: DebugElement = fixture.debugElement.query(By.css('#epersonsSearch tbody .fa-trash-alt'));
|
|
||||||
deleteButton.nativeElement.click();
|
|
||||||
tick();
|
|
||||||
fixture.detectChanges();
|
|
||||||
}));
|
|
||||||
it('then no ePerson is member of the active group', () => {
|
|
||||||
epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr'));
|
epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr'));
|
||||||
expect(epersonsFound.length).toEqual(2);
|
expect(epersonsFound.length).toEqual(0);
|
||||||
epersonsFound.map((foundEPersonRowElement: DebugElement) => {
|
|
||||||
const addButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-plus'));
|
|
||||||
const deleteButton: DebugElement = foundEPersonRowElement.query(By.css('td:last-child .fa-trash-alt'));
|
|
||||||
expect(deleteButton).toBeNull();
|
|
||||||
expect(addButton).not.toBeNull();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -4,28 +4,23 @@ import { Router } from '@angular/router';
|
|||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import {
|
import {
|
||||||
Observable,
|
Observable,
|
||||||
of as observableOf,
|
|
||||||
Subscription,
|
Subscription,
|
||||||
BehaviorSubject,
|
BehaviorSubject
|
||||||
combineLatest as observableCombineLatest,
|
|
||||||
ObservedValueOf,
|
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { defaultIfEmpty, map, mergeMap, switchMap, take } from 'rxjs/operators';
|
import { map, switchMap, take } from 'rxjs/operators';
|
||||||
import { buildPaginatedList, PaginatedList } from '../../../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../../../core/data/paginated-list.model';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
|
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
|
||||||
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
||||||
import { EPerson } from '../../../../core/eperson/models/eperson.model';
|
import { EPerson } from '../../../../core/eperson/models/eperson.model';
|
||||||
import { Group } from '../../../../core/eperson/models/group.model';
|
import { Group } from '../../../../core/eperson/models/group.model';
|
||||||
import {
|
import {
|
||||||
getFirstSucceededRemoteData,
|
|
||||||
getFirstCompletedRemoteData,
|
getFirstCompletedRemoteData,
|
||||||
getAllCompletedRemoteData,
|
getAllCompletedRemoteData,
|
||||||
getRemoteDataPayload
|
getRemoteDataPayload
|
||||||
} from '../../../../core/shared/operators';
|
} from '../../../../core/shared/operators';
|
||||||
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||||
import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model';
|
||||||
import { EpersonDtoModel } from '../../../../core/eperson/models/eperson-dto.model';
|
|
||||||
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
||||||
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
|
||||||
@@ -34,8 +29,8 @@ import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
|
|||||||
*/
|
*/
|
||||||
enum SubKey {
|
enum SubKey {
|
||||||
ActiveGroup,
|
ActiveGroup,
|
||||||
MembersDTO,
|
Members,
|
||||||
SearchResultsDTO,
|
SearchResults,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -96,11 +91,11 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
|||||||
/**
|
/**
|
||||||
* EPeople being displayed in search result, initially all members, after search result of search
|
* EPeople being displayed in search result, initially all members, after search result of search
|
||||||
*/
|
*/
|
||||||
ePeopleSearchDtos: BehaviorSubject<PaginatedList<EpersonDtoModel>> = new BehaviorSubject<PaginatedList<EpersonDtoModel>>(undefined);
|
ePeopleSearch: BehaviorSubject<PaginatedList<EPerson>> = new BehaviorSubject<PaginatedList<EPerson>>(undefined);
|
||||||
/**
|
/**
|
||||||
* List of EPeople members of currently active group being edited
|
* List of EPeople members of currently active group being edited
|
||||||
*/
|
*/
|
||||||
ePeopleMembersOfGroupDtos: BehaviorSubject<PaginatedList<EpersonDtoModel>> = new BehaviorSubject<PaginatedList<EpersonDtoModel>>(undefined);
|
ePeopleMembersOfGroup: BehaviorSubject<PaginatedList<EPerson>> = new BehaviorSubject<PaginatedList<EPerson>>(undefined);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pagination config used to display the list of EPeople that are result of EPeople search
|
* Pagination config used to display the list of EPeople that are result of EPeople search
|
||||||
@@ -129,7 +124,6 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
// Current search in edit group - epeople search form
|
// Current search in edit group - epeople search form
|
||||||
currentSearchQuery: string;
|
currentSearchQuery: string;
|
||||||
currentSearchScope: string;
|
|
||||||
|
|
||||||
// Whether or not user has done a EPeople search yet
|
// Whether or not user has done a EPeople search yet
|
||||||
searchDone: boolean;
|
searchDone: boolean;
|
||||||
@@ -148,18 +142,17 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
|||||||
public dsoNameService: DSONameService,
|
public dsoNameService: DSONameService,
|
||||||
) {
|
) {
|
||||||
this.currentSearchQuery = '';
|
this.currentSearchQuery = '';
|
||||||
this.currentSearchScope = 'metadata';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.searchForm = this.formBuilder.group(({
|
this.searchForm = this.formBuilder.group(({
|
||||||
scope: 'metadata',
|
|
||||||
query: '',
|
query: '',
|
||||||
}));
|
}));
|
||||||
this.subs.set(SubKey.ActiveGroup, this.groupDataService.getActiveGroup().subscribe((activeGroup: Group) => {
|
this.subs.set(SubKey.ActiveGroup, this.groupDataService.getActiveGroup().subscribe((activeGroup: Group) => {
|
||||||
if (activeGroup != null) {
|
if (activeGroup != null) {
|
||||||
this.groupBeingEdited = activeGroup;
|
this.groupBeingEdited = activeGroup;
|
||||||
this.retrieveMembers(this.config.currentPage);
|
this.retrieveMembers(this.config.currentPage);
|
||||||
|
this.search({query: ''});
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -171,8 +164,8 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
retrieveMembers(page: number): void {
|
retrieveMembers(page: number): void {
|
||||||
this.unsubFrom(SubKey.MembersDTO);
|
this.unsubFrom(SubKey.Members);
|
||||||
this.subs.set(SubKey.MembersDTO,
|
this.subs.set(SubKey.Members,
|
||||||
this.paginationService.getCurrentPagination(this.config.id, this.config).pipe(
|
this.paginationService.getCurrentPagination(this.config.id, this.config).pipe(
|
||||||
switchMap((currentPagination) => {
|
switchMap((currentPagination) => {
|
||||||
return this.ePersonDataService.findListByHref(this.groupBeingEdited._links.epersons.href, {
|
return this.ePersonDataService.findListByHref(this.groupBeingEdited._links.epersons.href, {
|
||||||
@@ -189,49 +182,12 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
|||||||
return rd;
|
return rd;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
switchMap((epersonListRD: RemoteData<PaginatedList<EPerson>>) => {
|
getRemoteDataPayload())
|
||||||
const dtos$ = observableCombineLatest([...epersonListRD.payload.page.map((member: EPerson) => {
|
.subscribe((paginatedListOfEPersons: PaginatedList<EPerson>) => {
|
||||||
const dto$: Observable<EpersonDtoModel> = observableCombineLatest(
|
this.ePeopleMembersOfGroup.next(paginatedListOfEPersons);
|
||||||
this.isMemberOfGroup(member), (isMember: ObservedValueOf<Observable<boolean>>) => {
|
|
||||||
const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel();
|
|
||||||
epersonDtoModel.eperson = member;
|
|
||||||
epersonDtoModel.memberOfGroup = isMember;
|
|
||||||
return epersonDtoModel;
|
|
||||||
});
|
|
||||||
return dto$;
|
|
||||||
})]);
|
|
||||||
return dtos$.pipe(defaultIfEmpty([]), map((dtos: EpersonDtoModel[]) => {
|
|
||||||
return buildPaginatedList(epersonListRD.payload.pageInfo, dtos);
|
|
||||||
}));
|
|
||||||
}))
|
|
||||||
.subscribe((paginatedListOfDTOs: PaginatedList<EpersonDtoModel>) => {
|
|
||||||
this.ePeopleMembersOfGroupDtos.next(paginatedListOfDTOs);
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the given ePerson is a member of the group currently being edited
|
|
||||||
* @param possibleMember EPerson that is a possible member (being tested) of the group currently being edited
|
|
||||||
*/
|
|
||||||
isMemberOfGroup(possibleMember: EPerson): Observable<boolean> {
|
|
||||||
return this.groupDataService.getActiveGroup().pipe(take(1),
|
|
||||||
mergeMap((group: Group) => {
|
|
||||||
if (group != null) {
|
|
||||||
return this.ePersonDataService.findListByHref(group._links.epersons.href, {
|
|
||||||
currentPage: 1,
|
|
||||||
elementsPerPage: 9999
|
|
||||||
})
|
|
||||||
.pipe(
|
|
||||||
getFirstSucceededRemoteData(),
|
|
||||||
getRemoteDataPayload(),
|
|
||||||
map((listEPeopleInGroup: PaginatedList<EPerson>) => listEPeopleInGroup.page.filter((ePersonInList: EPerson) => ePersonInList.id === possibleMember.id)),
|
|
||||||
map((epeople: EPerson[]) => epeople.length > 0));
|
|
||||||
} else {
|
|
||||||
return observableOf(false);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unsubscribe from a subscription if it's still subscribed, and remove it from the map of
|
* Unsubscribe from a subscription if it's still subscribed, and remove it from the map of
|
||||||
* active subscriptions
|
* active subscriptions
|
||||||
@@ -248,14 +204,18 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a given EPerson from the members list of the group currently being edited
|
* Deletes a given EPerson from the members list of the group currently being edited
|
||||||
* @param ePerson EPerson we want to delete as member from group that is currently being edited
|
* @param eperson EPerson we want to delete as member from group that is currently being edited
|
||||||
*/
|
*/
|
||||||
deleteMemberFromGroup(ePerson: EpersonDtoModel) {
|
deleteMemberFromGroup(eperson: EPerson) {
|
||||||
ePerson.memberOfGroup = false;
|
|
||||||
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => {
|
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => {
|
||||||
if (activeGroup != null) {
|
if (activeGroup != null) {
|
||||||
const response = this.groupDataService.deleteMemberFromGroup(activeGroup, ePerson.eperson);
|
const response = this.groupDataService.deleteMemberFromGroup(activeGroup, eperson);
|
||||||
this.showNotifications('deleteMember', response, this.dsoNameService.getName(ePerson.eperson), activeGroup);
|
this.showNotifications('deleteMember', response, this.dsoNameService.getName(eperson), activeGroup);
|
||||||
|
// Reload search results (if there is an active query).
|
||||||
|
// This will potentially add this deleted subgroup into the list of search results.
|
||||||
|
if (this.currentSearchQuery != null) {
|
||||||
|
this.search({query: this.currentSearchQuery});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveGroup'));
|
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveGroup'));
|
||||||
}
|
}
|
||||||
@@ -264,14 +224,18 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a given EPerson to the members list of the group currently being edited
|
* Adds a given EPerson to the members list of the group currently being edited
|
||||||
* @param ePerson EPerson we want to add as member to group that is currently being edited
|
* @param eperson EPerson we want to add as member to group that is currently being edited
|
||||||
*/
|
*/
|
||||||
addMemberToGroup(ePerson: EpersonDtoModel) {
|
addMemberToGroup(eperson: EPerson) {
|
||||||
ePerson.memberOfGroup = true;
|
|
||||||
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => {
|
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => {
|
||||||
if (activeGroup != null) {
|
if (activeGroup != null) {
|
||||||
const response = this.groupDataService.addMemberToGroup(activeGroup, ePerson.eperson);
|
const response = this.groupDataService.addMemberToGroup(activeGroup, eperson);
|
||||||
this.showNotifications('addMember', response, this.dsoNameService.getName(ePerson.eperson), activeGroup);
|
this.showNotifications('addMember', response, this.dsoNameService.getName(eperson), activeGroup);
|
||||||
|
// Reload search results (if there is an active query).
|
||||||
|
// This will potentially add this deleted subgroup into the list of search results.
|
||||||
|
if (this.currentSearchQuery != null) {
|
||||||
|
this.search({query: this.currentSearchQuery});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveGroup'));
|
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveGroup'));
|
||||||
}
|
}
|
||||||
@@ -279,37 +243,25 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search in the EPeople by name, email or metadata
|
* Search all EPeople who are NOT a member of the current group by name, email or metadata
|
||||||
* @param data Contains scope and query param
|
* @param data Contains query param
|
||||||
*/
|
*/
|
||||||
search(data: any) {
|
search(data: any) {
|
||||||
this.unsubFrom(SubKey.SearchResultsDTO);
|
this.unsubFrom(SubKey.SearchResults);
|
||||||
this.subs.set(SubKey.SearchResultsDTO,
|
this.subs.set(SubKey.SearchResults,
|
||||||
this.paginationService.getCurrentPagination(this.configSearch.id, this.configSearch).pipe(
|
this.paginationService.getCurrentPagination(this.configSearch.id, this.configSearch).pipe(
|
||||||
switchMap((paginationOptions) => {
|
switchMap((paginationOptions) => {
|
||||||
|
|
||||||
const query: string = data.query;
|
const query: string = data.query;
|
||||||
const scope: string = data.scope;
|
|
||||||
if (query != null && this.currentSearchQuery !== query && this.groupBeingEdited) {
|
if (query != null && this.currentSearchQuery !== query && this.groupBeingEdited) {
|
||||||
this.router.navigate([], {
|
|
||||||
queryParamsHandling: 'merge'
|
|
||||||
});
|
|
||||||
this.currentSearchQuery = query;
|
this.currentSearchQuery = query;
|
||||||
this.paginationService.resetPage(this.configSearch.id);
|
this.paginationService.resetPage(this.configSearch.id);
|
||||||
}
|
}
|
||||||
if (scope != null && this.currentSearchScope !== scope && this.groupBeingEdited) {
|
|
||||||
this.router.navigate([], {
|
|
||||||
queryParamsHandling: 'merge'
|
|
||||||
});
|
|
||||||
this.currentSearchScope = scope;
|
|
||||||
this.paginationService.resetPage(this.configSearch.id);
|
|
||||||
}
|
|
||||||
this.searchDone = true;
|
this.searchDone = true;
|
||||||
|
|
||||||
return this.ePersonDataService.searchByScope(this.currentSearchScope, this.currentSearchQuery, {
|
return this.ePersonDataService.searchNonMembers(this.currentSearchQuery, this.groupBeingEdited.id, {
|
||||||
currentPage: paginationOptions.currentPage,
|
currentPage: paginationOptions.currentPage,
|
||||||
elementsPerPage: paginationOptions.pageSize
|
elementsPerPage: paginationOptions.pageSize
|
||||||
});
|
}, false, true);
|
||||||
}),
|
}),
|
||||||
getAllCompletedRemoteData(),
|
getAllCompletedRemoteData(),
|
||||||
map((rd: RemoteData<any>) => {
|
map((rd: RemoteData<any>) => {
|
||||||
@@ -319,23 +271,9 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
|||||||
return rd;
|
return rd;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
switchMap((epersonListRD: RemoteData<PaginatedList<EPerson>>) => {
|
getRemoteDataPayload())
|
||||||
const dtos$ = observableCombineLatest([...epersonListRD.payload.page.map((member: EPerson) => {
|
.subscribe((paginatedListOfEPersons: PaginatedList<EPerson>) => {
|
||||||
const dto$: Observable<EpersonDtoModel> = observableCombineLatest(
|
this.ePeopleSearch.next(paginatedListOfEPersons);
|
||||||
this.isMemberOfGroup(member), (isMember: ObservedValueOf<Observable<boolean>>) => {
|
|
||||||
const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel();
|
|
||||||
epersonDtoModel.eperson = member;
|
|
||||||
epersonDtoModel.memberOfGroup = isMember;
|
|
||||||
return epersonDtoModel;
|
|
||||||
});
|
|
||||||
return dto$;
|
|
||||||
})]);
|
|
||||||
return dtos$.pipe(defaultIfEmpty([]), map((dtos: EpersonDtoModel[]) => {
|
|
||||||
return buildPaginatedList(epersonListRD.payload.pageInfo, dtos);
|
|
||||||
}));
|
|
||||||
}))
|
|
||||||
.subscribe((paginatedListOfDTOs: PaginatedList<EpersonDtoModel>) => {
|
|
||||||
this.ePeopleSearchDtos.next(paginatedListOfDTOs);
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,55 @@
|
|||||||
<ng-container>
|
<ng-container>
|
||||||
<h3 class="border-bottom pb-2">{{messagePrefix + '.head' | translate}}</h3>
|
<h3 class="border-bottom pb-2">{{messagePrefix + '.head' | translate}}</h3>
|
||||||
|
|
||||||
|
<h4>{{messagePrefix + '.headSubgroups' | translate}}</h4>
|
||||||
|
|
||||||
|
<ds-pagination *ngIf="(subGroups$ | async)?.payload?.totalElements > 0"
|
||||||
|
[paginationOptions]="config"
|
||||||
|
[pageInfoState]="(subGroups$ | async)?.payload"
|
||||||
|
[collectionSize]="(subGroups$ | async)?.payload?.totalElements"
|
||||||
|
[hideGear]="true"
|
||||||
|
[hidePagerWhenSinglePage]="true">
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="subgroupsOfGroup" class="table table-striped table-hover table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="align-middle">{{messagePrefix + '.table.id' | translate}}</th>
|
||||||
|
<th scope="col" class="align-middle">{{messagePrefix + '.table.name' | translate}}</th>
|
||||||
|
<th scope="col" class="align-middle">{{messagePrefix + '.table.collectionOrCommunity' | translate}}</th>
|
||||||
|
<th>{{messagePrefix + '.table.edit' | translate}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let group of (subGroups$ | async)?.payload?.page">
|
||||||
|
<td class="align-middle">{{group.id}}</td>
|
||||||
|
<td class="align-middle">
|
||||||
|
<a (click)="groupDataService.startEditingNewGroup(group)"
|
||||||
|
[routerLink]="[groupDataService.getGroupEditPageRouterLink(group)]">
|
||||||
|
{{ dsoNameService.getName(group) }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td class="align-middle">{{ dsoNameService.getName((group.object | async)?.payload)}}</td>
|
||||||
|
<td class="align-middle">
|
||||||
|
<div class="btn-group edit-field">
|
||||||
|
<button (click)="deleteSubgroupFromGroup(group)"
|
||||||
|
class="btn btn-outline-danger btn-sm deleteButton"
|
||||||
|
title="{{messagePrefix + '.table.edit.buttons.remove' | translate: { name: dsoNameService.getName(group) } }}">
|
||||||
|
<i class="fas fa-trash-alt fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</ds-pagination>
|
||||||
|
|
||||||
|
<div *ngIf="(subGroups$ | async)?.payload?.totalElements == 0" class="alert alert-info w-100 mb-2"
|
||||||
|
role="alert">
|
||||||
|
{{messagePrefix + '.no-subgroups-yet' | translate}}
|
||||||
|
</div>
|
||||||
|
|
||||||
<h4 id="search" class="border-bottom pb-2">
|
<h4 id="search" class="border-bottom pb-2">
|
||||||
<span *dsContextHelp="{
|
<span *dsContextHelp="{
|
||||||
content: 'admin.access-control.groups.form.tooltip.editGroup.addSubgroups',
|
content: 'admin.access-control.groups.form.tooltip.editGroup.addSubgroups',
|
||||||
@@ -62,17 +111,7 @@
|
|||||||
<td class="align-middle">{{ dsoNameService.getName((group.object | async)?.payload) }}</td>
|
<td class="align-middle">{{ dsoNameService.getName((group.object | async)?.payload) }}</td>
|
||||||
<td class="align-middle">
|
<td class="align-middle">
|
||||||
<div class="btn-group edit-field">
|
<div class="btn-group edit-field">
|
||||||
<button *ngIf="(isSubgroupOfGroup(group) | async) && !(isActiveGroup(group) | async)"
|
<button (click)="addSubgroupToGroup(group)"
|
||||||
(click)="deleteSubgroupFromGroup(group)"
|
|
||||||
class="btn btn-outline-danger btn-sm deleteButton"
|
|
||||||
title="{{messagePrefix + '.table.edit.buttons.remove' | translate: { name: dsoNameService.getName(group) } }}">
|
|
||||||
<i class="fas fa-trash-alt fa-fw"></i>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<span *ngIf="(isActiveGroup(group) | async)">{{ messagePrefix + '.table.edit.currentGroup' | translate }}</span>
|
|
||||||
|
|
||||||
<button *ngIf="!(isSubgroupOfGroup(group) | async) && !(isActiveGroup(group) | async)"
|
|
||||||
(click)="addSubgroupToGroup(group)"
|
|
||||||
class="btn btn-outline-primary btn-sm addButton"
|
class="btn btn-outline-primary btn-sm addButton"
|
||||||
title="{{messagePrefix + '.table.edit.buttons.add' | translate: { name: dsoNameService.getName(group) } }}">
|
title="{{messagePrefix + '.table.edit.buttons.add' | translate: { name: dsoNameService.getName(group) } }}">
|
||||||
<i class="fas fa-plus fa-fw"></i>
|
<i class="fas fa-plus fa-fw"></i>
|
||||||
@@ -90,53 +129,4 @@
|
|||||||
{{messagePrefix + '.no-items' | translate}}
|
{{messagePrefix + '.no-items' | translate}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h4>{{messagePrefix + '.headSubgroups' | translate}}</h4>
|
|
||||||
|
|
||||||
<ds-pagination *ngIf="(subGroups$ | async)?.payload?.totalElements > 0"
|
|
||||||
[paginationOptions]="config"
|
|
||||||
[pageInfoState]="(subGroups$ | async)?.payload"
|
|
||||||
[collectionSize]="(subGroups$ | async)?.payload?.totalElements"
|
|
||||||
[hideGear]="true"
|
|
||||||
[hidePagerWhenSinglePage]="true">
|
|
||||||
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table id="subgroupsOfGroup" class="table table-striped table-hover table-bordered">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.id' | translate}}</th>
|
|
||||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.name' | translate}}</th>
|
|
||||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.collectionOrCommunity' | translate}}</th>
|
|
||||||
<th>{{messagePrefix + '.table.edit' | translate}}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr *ngFor="let group of (subGroups$ | async)?.payload?.page">
|
|
||||||
<td class="align-middle">{{group.id}}</td>
|
|
||||||
<td class="align-middle">
|
|
||||||
<a (click)="groupDataService.startEditingNewGroup(group)"
|
|
||||||
[routerLink]="[groupDataService.getGroupEditPageRouterLink(group)]">
|
|
||||||
{{ dsoNameService.getName(group) }}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td class="align-middle">{{ dsoNameService.getName((group.object | async)?.payload)}}</td>
|
|
||||||
<td class="align-middle">
|
|
||||||
<div class="btn-group edit-field">
|
|
||||||
<button (click)="deleteSubgroupFromGroup(group)"
|
|
||||||
class="btn btn-outline-danger btn-sm deleteButton"
|
|
||||||
title="{{messagePrefix + '.table.edit.buttons.remove' | translate: { name: dsoNameService.getName(group) } }}">
|
|
||||||
<i class="fas fa-trash-alt fa-fw"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</ds-pagination>
|
|
||||||
|
|
||||||
<div *ngIf="(subGroups$ | async)?.payload?.totalElements == 0" class="alert alert-info w-100 mb-2"
|
|
||||||
role="alert">
|
|
||||||
{{messagePrefix + '.no-subgroups-yet' | translate}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { NO_ERRORS_SCHEMA, DebugElement } from '@angular/core';
|
import { NO_ERRORS_SCHEMA, DebugElement } from '@angular/core';
|
||||||
import { ComponentFixture, fakeAsync, flush, inject, TestBed, tick, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, fakeAsync, flush, inject, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { BrowserModule, By } from '@angular/platform-browser';
|
import { BrowserModule, By } from '@angular/platform-browser';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
|
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||||
import { Observable, of as observableOf, BehaviorSubject } from 'rxjs';
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
import { RestResponse } from '../../../../core/cache/response.models';
|
import { RestResponse } from '../../../../core/cache/response.models';
|
||||||
import { buildPaginatedList, PaginatedList } from '../../../../core/data/paginated-list.model';
|
import { buildPaginatedList, PaginatedList } from '../../../../core/data/paginated-list.model';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
@@ -18,19 +18,18 @@ import { NotificationsService } from '../../../../shared/notifications/notificat
|
|||||||
import { GroupMock, GroupMock2 } from '../../../../shared/testing/group-mock';
|
import { GroupMock, GroupMock2 } from '../../../../shared/testing/group-mock';
|
||||||
import { SubgroupsListComponent } from './subgroups-list.component';
|
import { SubgroupsListComponent } from './subgroups-list.component';
|
||||||
import {
|
import {
|
||||||
createSuccessfulRemoteDataObject$,
|
createSuccessfulRemoteDataObject$
|
||||||
createSuccessfulRemoteDataObject
|
|
||||||
} from '../../../../shared/remote-data.utils';
|
} from '../../../../shared/remote-data.utils';
|
||||||
import { RouterMock } from '../../../../shared/mocks/router.mock';
|
import { RouterMock } from '../../../../shared/mocks/router.mock';
|
||||||
import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder-service.mock';
|
import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder-service.mock';
|
||||||
import { getMockTranslateService } from '../../../../shared/mocks/translate.service.mock';
|
import { getMockTranslateService } from '../../../../shared/mocks/translate.service.mock';
|
||||||
import { TranslateLoaderMock } from '../../../../shared/testing/translate-loader.mock';
|
import { TranslateLoaderMock } from '../../../../shared/testing/translate-loader.mock';
|
||||||
import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
|
import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
||||||
import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub';
|
||||||
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
|
||||||
import { DSONameServiceMock } from '../../../../shared/mocks/dso-name.service.mock';
|
import { DSONameServiceMock } from '../../../../shared/mocks/dso-name.service.mock';
|
||||||
|
import { EPersonMock2 } from 'src/app/shared/testing/eperson.mock';
|
||||||
|
|
||||||
describe('SubgroupsListComponent', () => {
|
describe('SubgroupsListComponent', () => {
|
||||||
let component: SubgroupsListComponent;
|
let component: SubgroupsListComponent;
|
||||||
@@ -39,44 +38,70 @@ describe('SubgroupsListComponent', () => {
|
|||||||
let builderService: FormBuilderService;
|
let builderService: FormBuilderService;
|
||||||
let ePersonDataServiceStub: any;
|
let ePersonDataServiceStub: any;
|
||||||
let groupsDataServiceStub: any;
|
let groupsDataServiceStub: any;
|
||||||
let activeGroup;
|
let activeGroup: Group;
|
||||||
let subgroups: Group[];
|
let subgroups: Group[];
|
||||||
let allGroups: Group[];
|
let groupNonMembers: Group[];
|
||||||
let routerStub;
|
let routerStub;
|
||||||
let paginationService;
|
let paginationService;
|
||||||
|
// Define a new mock activegroup for all tests below
|
||||||
|
let mockActiveGroup: Group = Object.assign(new Group(), {
|
||||||
|
handle: null,
|
||||||
|
subgroups: [GroupMock2],
|
||||||
|
epersons: [EPersonMock2],
|
||||||
|
selfRegistered: false,
|
||||||
|
permanent: false,
|
||||||
|
_links: {
|
||||||
|
self: {
|
||||||
|
href: 'https://rest.api/server/api/eperson/groups/activegroupid',
|
||||||
|
},
|
||||||
|
subgroups: { href: 'https://rest.api/server/api/eperson/groups/activegroupid/subgroups' },
|
||||||
|
object: { href: 'https://rest.api/server/api/eperson/groups/activegroupid/object' },
|
||||||
|
epersons: { href: 'https://rest.api/server/api/eperson/groups/activegroupid/epersons' }
|
||||||
|
},
|
||||||
|
_name: 'activegroupname',
|
||||||
|
id: 'activegroupid',
|
||||||
|
uuid: 'activegroupid',
|
||||||
|
type: 'group',
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
activeGroup = GroupMock;
|
activeGroup = mockActiveGroup;
|
||||||
subgroups = [GroupMock2];
|
subgroups = [GroupMock2];
|
||||||
allGroups = [GroupMock, GroupMock2];
|
groupNonMembers = [GroupMock];
|
||||||
ePersonDataServiceStub = {};
|
ePersonDataServiceStub = {};
|
||||||
groupsDataServiceStub = {
|
groupsDataServiceStub = {
|
||||||
activeGroup: activeGroup,
|
activeGroup: activeGroup,
|
||||||
subgroups$: new BehaviorSubject(subgroups),
|
subgroups: subgroups,
|
||||||
|
groupNonMembers: groupNonMembers,
|
||||||
getActiveGroup(): Observable<Group> {
|
getActiveGroup(): Observable<Group> {
|
||||||
return observableOf(this.activeGroup);
|
return observableOf(this.activeGroup);
|
||||||
},
|
},
|
||||||
getSubgroups(): Group {
|
getSubgroups(): Group {
|
||||||
return this.activeGroup;
|
return this.subgroups;
|
||||||
},
|
},
|
||||||
|
// This method is used to get all the current subgroups
|
||||||
findListByHref(_href: string): Observable<RemoteData<PaginatedList<Group>>> {
|
findListByHref(_href: string): Observable<RemoteData<PaginatedList<Group>>> {
|
||||||
return this.subgroups$.pipe(
|
return createSuccessfulRemoteDataObject$(buildPaginatedList<Group>(new PageInfo(), groupsDataServiceStub.getSubgroups()));
|
||||||
map((currentGroups: Group[]) => {
|
|
||||||
return createSuccessfulRemoteDataObject(buildPaginatedList<Group>(new PageInfo(), currentGroups));
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
getGroupEditPageRouterLink(group: Group): string {
|
getGroupEditPageRouterLink(group: Group): string {
|
||||||
return '/access-control/groups/' + group.id;
|
return '/access-control/groups/' + group.id;
|
||||||
},
|
},
|
||||||
searchGroups(query: string): Observable<RemoteData<PaginatedList<Group>>> {
|
// This method is used to get all groups which are NOT currently a subgroup member
|
||||||
|
searchNonMemberGroups(query: string, group: string): Observable<RemoteData<PaginatedList<Group>>> {
|
||||||
if (query === '') {
|
if (query === '') {
|
||||||
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), allGroups));
|
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), groupNonMembers));
|
||||||
}
|
}
|
||||||
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []));
|
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []));
|
||||||
},
|
},
|
||||||
addSubGroupToGroup(parentGroup, subgroup: Group): Observable<RestResponse> {
|
addSubGroupToGroup(parentGroup, subgroupToAdd: Group): Observable<RestResponse> {
|
||||||
this.subgroups$.next([...this.subgroups$.getValue(), subgroup]);
|
// Add group to list of subgroups
|
||||||
|
this.subgroups = [...this.subgroups, subgroupToAdd];
|
||||||
|
// Remove group from list of non-members
|
||||||
|
this.groupNonMembers.forEach( (group: Group, index: number) => {
|
||||||
|
if (group.id === subgroupToAdd.id) {
|
||||||
|
this.groupNonMembers.splice(index, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
return observableOf(new RestResponse(true, 200, 'Success'));
|
return observableOf(new RestResponse(true, 200, 'Success'));
|
||||||
},
|
},
|
||||||
clearGroupsRequests() {
|
clearGroupsRequests() {
|
||||||
@@ -85,12 +110,15 @@ describe('SubgroupsListComponent', () => {
|
|||||||
clearGroupLinkRequests() {
|
clearGroupLinkRequests() {
|
||||||
// empty
|
// empty
|
||||||
},
|
},
|
||||||
deleteSubGroupFromGroup(parentGroup, subgroup: Group): Observable<RestResponse> {
|
deleteSubGroupFromGroup(parentGroup, subgroupToDelete: Group): Observable<RestResponse> {
|
||||||
this.subgroups$.next(this.subgroups$.getValue().filter((group: Group) => {
|
// Remove group from list of subgroups
|
||||||
if (group.id !== subgroup.id) {
|
this.subgroups.forEach( (group: Group, index: number) => {
|
||||||
return group;
|
if (group.id === subgroupToDelete.id) {
|
||||||
|
this.subgroups.splice(index, 1);
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
|
// Add group to list of non-members
|
||||||
|
this.groupNonMembers = [...this.groupNonMembers, subgroupToDelete];
|
||||||
return observableOf(new RestResponse(true, 200, 'Success'));
|
return observableOf(new RestResponse(true, 200, 'Success'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -99,7 +127,7 @@ describe('SubgroupsListComponent', () => {
|
|||||||
translateService = getMockTranslateService();
|
translateService = getMockTranslateService();
|
||||||
|
|
||||||
paginationService = new PaginationServiceStub();
|
paginationService = new PaginationServiceStub();
|
||||||
TestBed.configureTestingModule({
|
return TestBed.configureTestingModule({
|
||||||
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
|
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
|
||||||
TranslateModule.forRoot({
|
TranslateModule.forRoot({
|
||||||
loader: {
|
loader: {
|
||||||
@@ -137,30 +165,38 @@ describe('SubgroupsListComponent', () => {
|
|||||||
expect(comp).toBeDefined();
|
expect(comp).toBeDefined();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should show list of subgroups of current active group', () => {
|
describe('current subgroup list', () => {
|
||||||
const groupIdsFound = fixture.debugElement.queryAll(By.css('#subgroupsOfGroup tr td:first-child'));
|
it('should show list of subgroups of current active group', () => {
|
||||||
expect(groupIdsFound.length).toEqual(1);
|
const groupIdsFound = fixture.debugElement.queryAll(By.css('#subgroupsOfGroup tr td:first-child'));
|
||||||
activeGroup.subgroups.map((group: Group) => {
|
expect(groupIdsFound.length).toEqual(1);
|
||||||
expect(groupIdsFound.find((foundEl) => {
|
subgroups.map((group: Group) => {
|
||||||
return (foundEl.nativeElement.textContent.trim() === group.uuid);
|
expect(groupIdsFound.find((foundEl) => {
|
||||||
})).toBeTruthy();
|
return (foundEl.nativeElement.textContent.trim() === group.uuid);
|
||||||
});
|
})).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
describe('if first group delete button is pressed', () => {
|
|
||||||
let groupsFound: DebugElement[];
|
it('should show a delete button next to each subgroup', () => {
|
||||||
beforeEach(fakeAsync(() => {
|
const subgroupsFound = fixture.debugElement.queryAll(By.css('#subgroupsOfGroup tbody tr'));
|
||||||
const addButton = fixture.debugElement.query(By.css('#subgroupsOfGroup tbody .deleteButton'));
|
subgroupsFound.map((foundGroupRowElement: DebugElement) => {
|
||||||
addButton.triggerEventHandler('click', {
|
const addButton: DebugElement = foundGroupRowElement.query(By.css('td:last-child .fa-plus'));
|
||||||
preventDefault: () => {/**/
|
const deleteButton: DebugElement = foundGroupRowElement.query(By.css('td:last-child .fa-trash-alt'));
|
||||||
}
|
expect(addButton).toBeNull();
|
||||||
|
expect(deleteButton).not.toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('if first group delete button is pressed', () => {
|
||||||
|
let groupsFound: DebugElement[];
|
||||||
|
beforeEach(() => {
|
||||||
|
const deleteButton = fixture.debugElement.query(By.css('#subgroupsOfGroup tbody .deleteButton'));
|
||||||
|
deleteButton.nativeElement.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
it('then no subgroup remains as a member of the active group', () => {
|
||||||
|
groupsFound = fixture.debugElement.queryAll(By.css('#subgroupsOfGroup tbody tr'));
|
||||||
|
expect(groupsFound.length).toEqual(0);
|
||||||
});
|
});
|
||||||
tick();
|
|
||||||
fixture.detectChanges();
|
|
||||||
}));
|
|
||||||
it('one less subgroup in list from 1 to 0 (of 2 total groups)', () => {
|
|
||||||
groupsFound = fixture.debugElement.queryAll(By.css('#subgroupsOfGroup tbody tr'));
|
|
||||||
expect(groupsFound.length).toEqual(0);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -169,54 +205,38 @@ describe('SubgroupsListComponent', () => {
|
|||||||
let groupsFound: DebugElement[];
|
let groupsFound: DebugElement[];
|
||||||
beforeEach(fakeAsync(() => {
|
beforeEach(fakeAsync(() => {
|
||||||
component.search({ query: '' });
|
component.search({ query: '' });
|
||||||
|
fixture.detectChanges();
|
||||||
groupsFound = fixture.debugElement.queryAll(By.css('#groupsSearch tbody tr'));
|
groupsFound = fixture.debugElement.queryAll(By.css('#groupsSearch tbody tr'));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should display all groups', () => {
|
it('should display only non-member groups (i.e. groups that are not a subgroup)', () => {
|
||||||
fixture.detectChanges();
|
|
||||||
groupsFound = fixture.debugElement.queryAll(By.css('#groupsSearch tbody tr'));
|
|
||||||
expect(groupsFound.length).toEqual(2);
|
|
||||||
groupsFound = fixture.debugElement.queryAll(By.css('#groupsSearch tbody tr'));
|
|
||||||
const groupIdsFound: DebugElement[] = fixture.debugElement.queryAll(By.css('#groupsSearch tbody tr td:first-child'));
|
const groupIdsFound: DebugElement[] = fixture.debugElement.queryAll(By.css('#groupsSearch tbody tr td:first-child'));
|
||||||
allGroups.map((group: Group) => {
|
expect(groupIdsFound.length).toEqual(1);
|
||||||
|
groupNonMembers.map((group: Group) => {
|
||||||
expect(groupIdsFound.find((foundEl: DebugElement) => {
|
expect(groupIdsFound.find((foundEl: DebugElement) => {
|
||||||
return (foundEl.nativeElement.textContent.trim() === group.uuid);
|
return (foundEl.nativeElement.textContent.trim() === group.uuid);
|
||||||
})).toBeTruthy();
|
})).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('if group is already a subgroup', () => {
|
it('should display an add button next to non-member groups, not a delete button', () => {
|
||||||
it('should have delete button, else it should have add button', () => {
|
groupsFound.map((foundGroupRowElement: DebugElement) => {
|
||||||
|
const addButton: DebugElement = foundGroupRowElement.query(By.css('td:last-child .fa-plus'));
|
||||||
|
const deleteButton: DebugElement = foundGroupRowElement.query(By.css('td:last-child .fa-trash-alt'));
|
||||||
|
expect(addButton).not.toBeNull();
|
||||||
|
expect(deleteButton).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('if first add button is pressed', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const addButton: DebugElement = fixture.debugElement.query(By.css('#groupsSearch tbody .fa-plus'));
|
||||||
|
addButton.nativeElement.click();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
it('then all (two) Groups are subgroups of the active group. No non-members left', () => {
|
||||||
groupsFound = fixture.debugElement.queryAll(By.css('#groupsSearch tbody tr'));
|
groupsFound = fixture.debugElement.queryAll(By.css('#groupsSearch tbody tr'));
|
||||||
const getSubgroups = groupsDataServiceStub.getSubgroups().subgroups;
|
expect(groupsFound.length).toEqual(0);
|
||||||
if (getSubgroups !== undefined && getSubgroups.length > 0) {
|
|
||||||
groupsFound.map((foundGroupRowElement: DebugElement) => {
|
|
||||||
const groupId: DebugElement = foundGroupRowElement.query(By.css('td:first-child'));
|
|
||||||
const addButton: DebugElement = foundGroupRowElement.query(By.css('td:last-child .fa-plus'));
|
|
||||||
const deleteButton: DebugElement = foundGroupRowElement.query(By.css('td:last-child .fa-trash-alt'));
|
|
||||||
expect(addButton).toBeNull();
|
|
||||||
if (activeGroup.id === groupId.nativeElement.textContent) {
|
|
||||||
expect(deleteButton).toBeNull();
|
|
||||||
} else {
|
|
||||||
expect(deleteButton).not.toBeNull();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const subgroupIds: string[] = activeGroup.subgroups.map((group: Group) => group.id);
|
|
||||||
groupsFound.map((foundGroupRowElement: DebugElement) => {
|
|
||||||
const groupId: DebugElement = foundGroupRowElement.query(By.css('td:first-child'));
|
|
||||||
const addButton: DebugElement = foundGroupRowElement.query(By.css('td:last-child .fa-plus'));
|
|
||||||
const deleteButton: DebugElement = foundGroupRowElement.query(By.css('td:last-child .fa-trash-alt'));
|
|
||||||
if (subgroupIds.includes(groupId.nativeElement.textContent)) {
|
|
||||||
expect(addButton).toBeNull();
|
|
||||||
expect(deleteButton).not.toBeNull();
|
|
||||||
} else {
|
|
||||||
expect(deleteButton).toBeNull();
|
|
||||||
expect(addButton).not.toBeNull();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -2,16 +2,15 @@ import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
|||||||
import { UntypedFormBuilder } from '@angular/forms';
|
import { UntypedFormBuilder } from '@angular/forms';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { BehaviorSubject, Observable, of as observableOf, Subscription } from 'rxjs';
|
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||||
import { map, mergeMap, switchMap, take } from 'rxjs/operators';
|
import { map, switchMap, take } from 'rxjs/operators';
|
||||||
import { PaginatedList } from '../../../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../../../core/data/paginated-list.model';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
||||||
import { Group } from '../../../../core/eperson/models/group.model';
|
import { Group } from '../../../../core/eperson/models/group.model';
|
||||||
import {
|
import {
|
||||||
getFirstCompletedRemoteData,
|
getAllCompletedRemoteData,
|
||||||
getFirstSucceededRemoteData,
|
getFirstCompletedRemoteData
|
||||||
getRemoteDataPayload
|
|
||||||
} from '../../../../core/shared/operators';
|
} from '../../../../core/shared/operators';
|
||||||
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||||
import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model';
|
||||||
@@ -103,6 +102,7 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
|
|||||||
if (activeGroup != null) {
|
if (activeGroup != null) {
|
||||||
this.groupBeingEdited = activeGroup;
|
this.groupBeingEdited = activeGroup;
|
||||||
this.retrieveSubGroups();
|
this.retrieveSubGroups();
|
||||||
|
this.search({query: ''});
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -131,47 +131,6 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not the given group is a subgroup of the group currently being edited
|
|
||||||
* @param possibleSubgroup Group that is a possible subgroup (being tested) of the group currently being edited
|
|
||||||
*/
|
|
||||||
isSubgroupOfGroup(possibleSubgroup: Group): Observable<boolean> {
|
|
||||||
return this.groupDataService.getActiveGroup().pipe(take(1),
|
|
||||||
mergeMap((activeGroup: Group) => {
|
|
||||||
if (activeGroup != null) {
|
|
||||||
if (activeGroup.uuid === possibleSubgroup.uuid) {
|
|
||||||
return observableOf(false);
|
|
||||||
} else {
|
|
||||||
return this.groupDataService.findListByHref(activeGroup._links.subgroups.href, {
|
|
||||||
currentPage: 1,
|
|
||||||
elementsPerPage: 9999
|
|
||||||
})
|
|
||||||
.pipe(
|
|
||||||
getFirstSucceededRemoteData(),
|
|
||||||
getRemoteDataPayload(),
|
|
||||||
map((listTotalGroups: PaginatedList<Group>) => listTotalGroups.page.filter((groupInList: Group) => groupInList.id === possibleSubgroup.id)),
|
|
||||||
map((groups: Group[]) => groups.length > 0));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return observableOf(false);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not the given group is the current group being edited
|
|
||||||
* @param group Group that is possibly the current group being edited
|
|
||||||
*/
|
|
||||||
isActiveGroup(group: Group): Observable<boolean> {
|
|
||||||
return this.groupDataService.getActiveGroup().pipe(take(1),
|
|
||||||
mergeMap((activeGroup: Group) => {
|
|
||||||
if (activeGroup != null && activeGroup.uuid === group.uuid) {
|
|
||||||
return observableOf(true);
|
|
||||||
}
|
|
||||||
return observableOf(false);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes given subgroup from the group currently being edited
|
* Deletes given subgroup from the group currently being edited
|
||||||
* @param subgroup Group we want to delete from the subgroups of the group currently being edited
|
* @param subgroup Group we want to delete from the subgroups of the group currently being edited
|
||||||
@@ -181,6 +140,11 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
|
|||||||
if (activeGroup != null) {
|
if (activeGroup != null) {
|
||||||
const response = this.groupDataService.deleteSubGroupFromGroup(activeGroup, subgroup);
|
const response = this.groupDataService.deleteSubGroupFromGroup(activeGroup, subgroup);
|
||||||
this.showNotifications('deleteSubgroup', response, this.dsoNameService.getName(subgroup), activeGroup);
|
this.showNotifications('deleteSubgroup', response, this.dsoNameService.getName(subgroup), activeGroup);
|
||||||
|
// Reload search results (if there is an active query).
|
||||||
|
// This will potentially add this deleted subgroup into the list of search results.
|
||||||
|
if (this.currentSearchQuery != null) {
|
||||||
|
this.search({query: this.currentSearchQuery});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveGroup'));
|
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveGroup'));
|
||||||
}
|
}
|
||||||
@@ -197,6 +161,11 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
|
|||||||
if (activeGroup.uuid !== subgroup.uuid) {
|
if (activeGroup.uuid !== subgroup.uuid) {
|
||||||
const response = this.groupDataService.addSubGroupToGroup(activeGroup, subgroup);
|
const response = this.groupDataService.addSubGroupToGroup(activeGroup, subgroup);
|
||||||
this.showNotifications('addSubgroup', response, this.dsoNameService.getName(subgroup), activeGroup);
|
this.showNotifications('addSubgroup', response, this.dsoNameService.getName(subgroup), activeGroup);
|
||||||
|
// Reload search results (if there is an active query).
|
||||||
|
// This will potentially remove this added subgroup from search results.
|
||||||
|
if (this.currentSearchQuery != null) {
|
||||||
|
this.search({query: this.currentSearchQuery});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.subgroupToAddIsActiveGroup'));
|
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.subgroupToAddIsActiveGroup'));
|
||||||
}
|
}
|
||||||
@@ -207,28 +176,38 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search in the groups (searches by group name and by uuid exact match)
|
* Search all non-member groups (searches by group name and by uuid exact match). Used to search for
|
||||||
|
* groups that could be added to current group as a subgroup.
|
||||||
* @param data Contains query param
|
* @param data Contains query param
|
||||||
*/
|
*/
|
||||||
search(data: any) {
|
search(data: any) {
|
||||||
const query: string = data.query;
|
|
||||||
if (query != null && this.currentSearchQuery !== query) {
|
|
||||||
this.router.navigateByUrl(this.groupDataService.getGroupEditPageRouterLink(this.groupBeingEdited));
|
|
||||||
this.currentSearchQuery = query;
|
|
||||||
this.configSearch.currentPage = 1;
|
|
||||||
}
|
|
||||||
this.searchDone = true;
|
|
||||||
|
|
||||||
this.unsubFrom(SubKey.SearchResults);
|
this.unsubFrom(SubKey.SearchResults);
|
||||||
this.subs.set(SubKey.SearchResults, this.paginationService.getCurrentPagination(this.configSearch.id, this.configSearch).pipe(
|
this.subs.set(SubKey.SearchResults,
|
||||||
switchMap((config) => this.groupDataService.searchGroups(this.currentSearchQuery, {
|
this.paginationService.getCurrentPagination(this.configSearch.id, this.configSearch).pipe(
|
||||||
currentPage: config.currentPage,
|
switchMap((paginationOptions) => {
|
||||||
elementsPerPage: config.pageSize
|
const query: string = data.query;
|
||||||
}, true, true, followLink('object')
|
if (query != null && this.currentSearchQuery !== query && this.groupBeingEdited) {
|
||||||
))
|
this.currentSearchQuery = query;
|
||||||
).subscribe((rd: RemoteData<PaginatedList<Group>>) => {
|
this.paginationService.resetPage(this.configSearch.id);
|
||||||
this.searchResults$.next(rd);
|
}
|
||||||
}));
|
this.searchDone = true;
|
||||||
|
|
||||||
|
return this.groupDataService.searchNonMemberGroups(this.currentSearchQuery, this.groupBeingEdited.id, {
|
||||||
|
currentPage: paginationOptions.currentPage,
|
||||||
|
elementsPerPage: paginationOptions.pageSize
|
||||||
|
}, false, true, followLink('object'));
|
||||||
|
}),
|
||||||
|
getAllCompletedRemoteData(),
|
||||||
|
map((rd: RemoteData<any>) => {
|
||||||
|
if (rd.hasFailed) {
|
||||||
|
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure', { cause: rd.errorMessage }));
|
||||||
|
} else {
|
||||||
|
return rd;
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.subscribe((rd: RemoteData<PaginatedList<Group>>) => {
|
||||||
|
this.searchResults$.next(rd);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -216,18 +216,28 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the members (epersons embedded value of a group)
|
* Get the members (epersons embedded value of a group)
|
||||||
|
* NOTE: At this time we only grab the *first* member in order to receive the `totalElements` value
|
||||||
|
* needed for our HTML template.
|
||||||
* @param group
|
* @param group
|
||||||
*/
|
*/
|
||||||
getMembers(group: Group): Observable<RemoteData<PaginatedList<EPerson>>> {
|
getMembers(group: Group): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
return this.ePersonDataService.findListByHref(group._links.epersons.href).pipe(getFirstSucceededRemoteData());
|
return this.ePersonDataService.findListByHref(group._links.epersons.href, {
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: 1,
|
||||||
|
}).pipe(getFirstSucceededRemoteData());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the subgroups (groups embedded value of a group)
|
* Get the subgroups (groups embedded value of a group)
|
||||||
|
* NOTE: At this time we only grab the *first* subgroup in order to receive the `totalElements` value
|
||||||
|
* needed for our HTML template.
|
||||||
* @param group
|
* @param group
|
||||||
*/
|
*/
|
||||||
getSubgroups(group: Group): Observable<RemoteData<PaginatedList<Group>>> {
|
getSubgroups(group: Group): Observable<RemoteData<PaginatedList<Group>>> {
|
||||||
return this.groupService.findListByHref(group._links.subgroups.href).pipe(getFirstSucceededRemoteData());
|
return this.groupService.findListByHref(group._links.subgroups.href, {
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: 1,
|
||||||
|
}).pipe(getFirstSucceededRemoteData());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -11,6 +11,7 @@ import {
|
|||||||
EPeopleRegistryCancelEPersonAction,
|
EPeopleRegistryCancelEPersonAction,
|
||||||
EPeopleRegistryEditEPersonAction
|
EPeopleRegistryEditEPersonAction
|
||||||
} from '../../access-control/epeople-registry/epeople-registry.actions';
|
} from '../../access-control/epeople-registry/epeople-registry.actions';
|
||||||
|
import { GroupMock } from '../../shared/testing/group-mock';
|
||||||
import { RequestParam } from '../cache/models/request-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
import { ChangeAnalyzer } from '../data/change-analyzer';
|
import { ChangeAnalyzer } from '../data/change-analyzer';
|
||||||
import { PatchRequest, PostRequest } from '../data/request.models';
|
import { PatchRequest, PostRequest } from '../data/request.models';
|
||||||
@@ -140,6 +141,30 @@ describe('EPersonDataService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('searchNonMembers', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, 'searchBy');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('search with empty query and a group ID', () => {
|
||||||
|
service.searchNonMembers('', GroupMock.id);
|
||||||
|
const options = Object.assign(new FindListOptions(), {
|
||||||
|
searchParams: [Object.assign(new RequestParam('query', '')),
|
||||||
|
Object.assign(new RequestParam('group', GroupMock.id))]
|
||||||
|
});
|
||||||
|
expect(service.searchBy).toHaveBeenCalledWith('isNotMemberOf', options, true, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('search with query and a group ID', () => {
|
||||||
|
service.searchNonMembers('test', GroupMock.id);
|
||||||
|
const options = Object.assign(new FindListOptions(), {
|
||||||
|
searchParams: [Object.assign(new RequestParam('query', 'test')),
|
||||||
|
Object.assign(new RequestParam('group', GroupMock.id))]
|
||||||
|
});
|
||||||
|
expect(service.searchBy).toHaveBeenCalledWith('isNotMemberOf', options, true, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('updateEPerson', () => {
|
describe('updateEPerson', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(service, 'findByHref').and.returnValue(createSuccessfulRemoteDataObject$(EPersonMock));
|
spyOn(service, 'findByHref').and.returnValue(createSuccessfulRemoteDataObject$(EPersonMock));
|
||||||
|
@@ -177,6 +177,34 @@ export class EPersonDataService extends IdentifiableDataService<EPerson> impleme
|
|||||||
return this.searchBy(searchMethod, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
return this.searchBy(searchMethod, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches for all EPerons which are *not* a member of a given group, via a passed in query
|
||||||
|
* (searches all EPerson metadata and by exact UUID).
|
||||||
|
* Endpoint used: /eperson/epesons/search/isNotMemberOf?query=<:string>&group=<:uuid>
|
||||||
|
* @param query search query param
|
||||||
|
* @param group UUID of group to exclude results from. Members of this group will never be returned.
|
||||||
|
* @param options
|
||||||
|
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||||
|
* no valid cached version. Defaults to true
|
||||||
|
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||||
|
* requested after the response becomes stale
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||||
|
* {@link HALLink}s should be automatically resolved
|
||||||
|
*/
|
||||||
|
public searchNonMembers(query: string, group: string, options?: FindListOptions, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<EPerson>[]): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
|
const searchParams = [new RequestParam('query', query), new RequestParam('group', group)];
|
||||||
|
let findListOptions = new FindListOptions();
|
||||||
|
if (options) {
|
||||||
|
findListOptions = Object.assign(new FindListOptions(), options);
|
||||||
|
}
|
||||||
|
if (findListOptions.searchParams) {
|
||||||
|
findListOptions.searchParams = [...findListOptions.searchParams, ...searchParams];
|
||||||
|
} else {
|
||||||
|
findListOptions.searchParams = searchParams;
|
||||||
|
}
|
||||||
|
return this.searchBy('isNotMemberOf', findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new patch to the object cache
|
* Add a new patch to the object cache
|
||||||
* The patch is derived from the differences between the given object and its version in the object cache
|
* The patch is derived from the differences between the given object and its version in the object cache
|
||||||
|
@@ -43,11 +43,11 @@ describe('GroupDataService', () => {
|
|||||||
let rdbService;
|
let rdbService;
|
||||||
let objectCache;
|
let objectCache;
|
||||||
function init() {
|
function init() {
|
||||||
restEndpointURL = 'https://dspace.4science.it/dspace-spring-rest/api/eperson';
|
restEndpointURL = 'https://rest.api/server/api/eperson';
|
||||||
groupsEndpoint = `${restEndpointURL}/groups`;
|
groupsEndpoint = `${restEndpointURL}/groups`;
|
||||||
groups = [GroupMock, GroupMock2];
|
groups = [GroupMock, GroupMock2];
|
||||||
groups$ = createSuccessfulRemoteDataObject$(createPaginatedList(groups));
|
groups$ = createSuccessfulRemoteDataObject$(createPaginatedList(groups));
|
||||||
rdbService = getMockRemoteDataBuildServiceHrefMap(undefined, { 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups': groups$ });
|
rdbService = getMockRemoteDataBuildServiceHrefMap(undefined, { 'https://rest.api/server/api/eperson/groups': groups$ });
|
||||||
halService = new HALEndpointServiceStub(restEndpointURL);
|
halService = new HALEndpointServiceStub(restEndpointURL);
|
||||||
objectCache = getMockObjectCacheService();
|
objectCache = getMockObjectCacheService();
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@@ -111,6 +111,30 @@ describe('GroupDataService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('searchNonMemberGroups', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, 'searchBy');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('search with empty query and a group ID', () => {
|
||||||
|
service.searchNonMemberGroups('', GroupMock.id);
|
||||||
|
const options = Object.assign(new FindListOptions(), {
|
||||||
|
searchParams: [Object.assign(new RequestParam('query', '')),
|
||||||
|
Object.assign(new RequestParam('group', GroupMock.id))]
|
||||||
|
});
|
||||||
|
expect(service.searchBy).toHaveBeenCalledWith('isNotMemberOf', options, true, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('search with query and a group ID', () => {
|
||||||
|
service.searchNonMemberGroups('test', GroupMock.id);
|
||||||
|
const options = Object.assign(new FindListOptions(), {
|
||||||
|
searchParams: [Object.assign(new RequestParam('query', 'test')),
|
||||||
|
Object.assign(new RequestParam('group', GroupMock.id))]
|
||||||
|
});
|
||||||
|
expect(service.searchBy).toHaveBeenCalledWith('isNotMemberOf', options, true, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('addSubGroupToGroup', () => {
|
describe('addSubGroupToGroup', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
objectCache.getByHref.and.returnValue(observableOf({
|
objectCache.getByHref.and.returnValue(observableOf({
|
||||||
|
@@ -3,7 +3,7 @@ import { Injectable } from '@angular/core';
|
|||||||
|
|
||||||
import { createSelector, select, Store } from '@ngrx/store';
|
import { createSelector, select, Store } from '@ngrx/store';
|
||||||
import { Observable, zip as observableZip } from 'rxjs';
|
import { Observable, zip as observableZip } from 'rxjs';
|
||||||
import { filter, map, take } from 'rxjs/operators';
|
import { take } from 'rxjs/operators';
|
||||||
import {
|
import {
|
||||||
GroupRegistryCancelGroupAction,
|
GroupRegistryCancelGroupAction,
|
||||||
GroupRegistryEditGroupAction
|
GroupRegistryEditGroupAction
|
||||||
@@ -105,23 +105,31 @@ export class GroupDataService extends IdentifiableDataService<Group> implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the current user is member of to the indicated group
|
* Searches for all groups which are *not* a member of a given group, via a passed in query
|
||||||
*
|
* (searches in group name and by exact UUID).
|
||||||
* @param groupName
|
* Endpoint used: /eperson/groups/search/isNotMemberOf?query=<:string>&group=<:uuid>
|
||||||
* the group name
|
* @param query search query param
|
||||||
* @return boolean
|
* @param group UUID of group to exclude results from. Members of this group will never be returned.
|
||||||
* true if user is member of the indicated group, false otherwise
|
* @param options
|
||||||
|
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||||
|
* no valid cached version. Defaults to true
|
||||||
|
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||||
|
* requested after the response becomes stale
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||||
|
* {@link HALLink}s should be automatically resolved
|
||||||
*/
|
*/
|
||||||
isMemberOf(groupName: string): Observable<boolean> {
|
public searchNonMemberGroups(query: string, group: string, options?: FindListOptions, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Group>[]): Observable<RemoteData<PaginatedList<Group>>> {
|
||||||
const searchHref = 'isMemberOf';
|
const searchParams = [new RequestParam('query', query), new RequestParam('group', group)];
|
||||||
const options = new FindListOptions();
|
let findListOptions = new FindListOptions();
|
||||||
options.searchParams = [new RequestParam('groupName', groupName)];
|
if (options) {
|
||||||
|
findListOptions = Object.assign(new FindListOptions(), options);
|
||||||
return this.searchBy(searchHref, options).pipe(
|
}
|
||||||
filter((groups: RemoteData<PaginatedList<Group>>) => !groups.isResponsePending),
|
if (findListOptions.searchParams) {
|
||||||
take(1),
|
findListOptions.searchParams = [...findListOptions.searchParams, ...searchParams];
|
||||||
map((groups: RemoteData<PaginatedList<Group>>) => groups.payload.totalElements > 0)
|
} else {
|
||||||
);
|
findListOptions.searchParams = searchParams;
|
||||||
|
}
|
||||||
|
return this.searchBy('isNotMemberOf', findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -13,9 +13,4 @@ export class EpersonDtoModel {
|
|||||||
* Whether or not the linked EPerson is able to be deleted
|
* Whether or not the linked EPerson is able to be deleted
|
||||||
*/
|
*/
|
||||||
public ableToDelete: boolean;
|
public ableToDelete: boolean;
|
||||||
/**
|
|
||||||
* Whether or not this EPerson is member of group on page it is being used on
|
|
||||||
*/
|
|
||||||
public memberOfGroup: boolean;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -17,7 +17,7 @@ import { Group } from '../../../../core/eperson/models/group.model';
|
|||||||
import { PageInfo } from '../../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||||
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
|
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
|
||||||
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||||
import { GroupMock, GroupMock2 } from '../../../../shared/testing/group-mock';
|
import { GroupMock } from '../../../../shared/testing/group-mock';
|
||||||
import { ReviewersListComponent } from './reviewers-list.component';
|
import { ReviewersListComponent } from './reviewers-list.component';
|
||||||
import { EPersonMock, EPersonMock2 } from '../../../../shared/testing/eperson.mock';
|
import { EPersonMock, EPersonMock2 } from '../../../../shared/testing/eperson.mock';
|
||||||
import {
|
import {
|
||||||
@@ -31,8 +31,10 @@ import { NotificationsServiceStub } from '../../../../shared/testing/notificatio
|
|||||||
import { RouterMock } from '../../../../shared/mocks/router.mock';
|
import { RouterMock } from '../../../../shared/mocks/router.mock';
|
||||||
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
||||||
import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub';
|
||||||
import { EpersonDtoModel } from '../../../../core/eperson/models/eperson-dto.model';
|
|
||||||
|
|
||||||
|
// NOTE: Because ReviewersListComponent extends MembersListComponent, the below tests ONLY validate
|
||||||
|
// features which are *unique* to ReviewersListComponent. All other features are tested in the
|
||||||
|
// members-list.component.spec.ts file.
|
||||||
describe('ReviewersListComponent', () => {
|
describe('ReviewersListComponent', () => {
|
||||||
let component: ReviewersListComponent;
|
let component: ReviewersListComponent;
|
||||||
let fixture: ComponentFixture<ReviewersListComponent>;
|
let fixture: ComponentFixture<ReviewersListComponent>;
|
||||||
@@ -40,31 +42,27 @@ describe('ReviewersListComponent', () => {
|
|||||||
let builderService: FormBuilderService;
|
let builderService: FormBuilderService;
|
||||||
let ePersonDataServiceStub: any;
|
let ePersonDataServiceStub: any;
|
||||||
let groupsDataServiceStub: any;
|
let groupsDataServiceStub: any;
|
||||||
let activeGroup;
|
let activeGroup: Group;
|
||||||
let allEPersons;
|
let epersonMembers: EPerson[];
|
||||||
let allGroups;
|
let epersonNonMembers: EPerson[];
|
||||||
let epersonMembers;
|
|
||||||
let subgroupMembers;
|
|
||||||
let paginationService;
|
let paginationService;
|
||||||
let ePersonDtoModel1: EpersonDtoModel;
|
|
||||||
let ePersonDtoModel2: EpersonDtoModel;
|
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
activeGroup = GroupMock;
|
activeGroup = GroupMock;
|
||||||
epersonMembers = [EPersonMock2];
|
epersonMembers = [EPersonMock2];
|
||||||
subgroupMembers = [GroupMock2];
|
epersonNonMembers = [EPersonMock];
|
||||||
allEPersons = [EPersonMock, EPersonMock2];
|
|
||||||
allGroups = [GroupMock, GroupMock2];
|
|
||||||
ePersonDataServiceStub = {
|
ePersonDataServiceStub = {
|
||||||
activeGroup: activeGroup,
|
activeGroup: activeGroup,
|
||||||
epersonMembers: epersonMembers,
|
epersonMembers: epersonMembers,
|
||||||
subgroupMembers: subgroupMembers,
|
epersonNonMembers: epersonNonMembers,
|
||||||
|
// This method is used to get all the current members
|
||||||
findListByHref(_href: string): Observable<RemoteData<PaginatedList<EPerson>>> {
|
findListByHref(_href: string): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
return createSuccessfulRemoteDataObject$(buildPaginatedList<EPerson>(new PageInfo(), groupsDataServiceStub.getEPersonMembers()));
|
return createSuccessfulRemoteDataObject$(buildPaginatedList<EPerson>(new PageInfo(), groupsDataServiceStub.getEPersonMembers()));
|
||||||
},
|
},
|
||||||
|
// This method is used to search across *non-members*
|
||||||
searchByScope(scope: string, query: string): Observable<RemoteData<PaginatedList<EPerson>>> {
|
searchByScope(scope: string, query: string): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
if (query === '') {
|
if (query === '') {
|
||||||
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), allEPersons));
|
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), epersonNonMembers));
|
||||||
}
|
}
|
||||||
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []));
|
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []));
|
||||||
},
|
},
|
||||||
@@ -81,22 +79,22 @@ describe('ReviewersListComponent', () => {
|
|||||||
groupsDataServiceStub = {
|
groupsDataServiceStub = {
|
||||||
activeGroup: activeGroup,
|
activeGroup: activeGroup,
|
||||||
epersonMembers: epersonMembers,
|
epersonMembers: epersonMembers,
|
||||||
subgroupMembers: subgroupMembers,
|
epersonNonMembers: epersonNonMembers,
|
||||||
allGroups: allGroups,
|
|
||||||
getActiveGroup(): Observable<Group> {
|
getActiveGroup(): Observable<Group> {
|
||||||
return observableOf(activeGroup);
|
return observableOf(activeGroup);
|
||||||
},
|
},
|
||||||
getEPersonMembers() {
|
getEPersonMembers() {
|
||||||
return this.epersonMembers;
|
return this.epersonMembers;
|
||||||
},
|
},
|
||||||
searchGroups(query: string): Observable<RemoteData<PaginatedList<Group>>> {
|
addMemberToGroup(parentGroup, epersonToAdd: EPerson): Observable<RestResponse> {
|
||||||
if (query === '') {
|
// Add eperson to list of members
|
||||||
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), this.allGroups));
|
this.epersonMembers = [...this.epersonMembers, epersonToAdd];
|
||||||
}
|
// Remove eperson from list of non-members
|
||||||
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []));
|
this.epersonNonMembers.forEach( (eperson: EPerson, index: number) => {
|
||||||
},
|
if (eperson.id === epersonToAdd.id) {
|
||||||
addMemberToGroup(parentGroup, eperson: EPerson): Observable<RestResponse> {
|
this.epersonNonMembers.splice(index, 1);
|
||||||
this.epersonMembers = [...this.epersonMembers, eperson];
|
}
|
||||||
|
});
|
||||||
return observableOf(new RestResponse(true, 200, 'Success'));
|
return observableOf(new RestResponse(true, 200, 'Success'));
|
||||||
},
|
},
|
||||||
clearGroupsRequests() {
|
clearGroupsRequests() {
|
||||||
@@ -109,21 +107,20 @@ describe('ReviewersListComponent', () => {
|
|||||||
return '/access-control/groups/' + group.id;
|
return '/access-control/groups/' + group.id;
|
||||||
},
|
},
|
||||||
deleteMemberFromGroup(parentGroup, epersonToDelete: EPerson): Observable<RestResponse> {
|
deleteMemberFromGroup(parentGroup, epersonToDelete: EPerson): Observable<RestResponse> {
|
||||||
this.epersonMembers = this.epersonMembers.find((eperson: EPerson) => {
|
// Remove eperson from list of members
|
||||||
if (eperson.id !== epersonToDelete.id) {
|
this.epersonMembers.forEach( (eperson: EPerson, index: number) => {
|
||||||
return eperson;
|
if (eperson.id === epersonToDelete.id) {
|
||||||
|
this.epersonMembers.splice(index, 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (this.epersonMembers === undefined) {
|
// Add eperson to list of non-members
|
||||||
this.epersonMembers = [];
|
this.epersonNonMembers = [...this.epersonNonMembers, epersonToDelete];
|
||||||
}
|
|
||||||
return observableOf(new RestResponse(true, 200, 'Success'));
|
return observableOf(new RestResponse(true, 200, 'Success'));
|
||||||
},
|
},
|
||||||
|
// Used to find the currently active group
|
||||||
findById(id: string) {
|
findById(id: string) {
|
||||||
for (const group of allGroups) {
|
if (activeGroup.id === id) {
|
||||||
if (group.id === id) {
|
return createSuccessfulRemoteDataObject$(activeGroup);
|
||||||
return createSuccessfulRemoteDataObject$(group);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return createNoContentRemoteDataObject$();
|
return createNoContentRemoteDataObject$();
|
||||||
},
|
},
|
||||||
@@ -135,7 +132,7 @@ describe('ReviewersListComponent', () => {
|
|||||||
translateService = getMockTranslateService();
|
translateService = getMockTranslateService();
|
||||||
|
|
||||||
paginationService = new PaginationServiceStub();
|
paginationService = new PaginationServiceStub();
|
||||||
TestBed.configureTestingModule({
|
return TestBed.configureTestingModule({
|
||||||
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
|
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
|
||||||
TranslateModule.forRoot({
|
TranslateModule.forRoot({
|
||||||
loader: {
|
loader: {
|
||||||
@@ -169,12 +166,6 @@ describe('ReviewersListComponent', () => {
|
|||||||
fixture.debugElement.nativeElement.remove();
|
fixture.debugElement.nativeElement.remove();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
ePersonDtoModel1 = new EpersonDtoModel();
|
|
||||||
ePersonDtoModel1.eperson = EPersonMock;
|
|
||||||
ePersonDtoModel2 = new EpersonDtoModel();
|
|
||||||
ePersonDtoModel2.eperson = EPersonMock2;
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when no group is selected', () => {
|
describe('when no group is selected', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -218,34 +209,32 @@ describe('ReviewersListComponent', () => {
|
|||||||
it('should replace the value when a new member is added when multipleReviewers is false', () => {
|
it('should replace the value when a new member is added when multipleReviewers is false', () => {
|
||||||
spyOn(component.selectedReviewersUpdated, 'emit');
|
spyOn(component.selectedReviewersUpdated, 'emit');
|
||||||
component.multipleReviewers = false;
|
component.multipleReviewers = false;
|
||||||
component.selectedReviewers = [ePersonDtoModel1];
|
component.selectedReviewers = [EPersonMock];
|
||||||
|
|
||||||
component.addMemberToGroup(ePersonDtoModel2);
|
component.addMemberToGroup(EPersonMock2);
|
||||||
|
|
||||||
expect(component.selectedReviewers).toEqual([ePersonDtoModel2]);
|
expect(component.selectedReviewers).toEqual([EPersonMock2]);
|
||||||
expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([ePersonDtoModel2.eperson]);
|
expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([EPersonMock2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add the value when a new member is added when multipleReviewers is true', () => {
|
it('should add the value when a new member is added when multipleReviewers is true', () => {
|
||||||
spyOn(component.selectedReviewersUpdated, 'emit');
|
spyOn(component.selectedReviewersUpdated, 'emit');
|
||||||
component.multipleReviewers = true;
|
component.multipleReviewers = true;
|
||||||
component.selectedReviewers = [ePersonDtoModel1];
|
component.selectedReviewers = [EPersonMock];
|
||||||
|
|
||||||
component.addMemberToGroup(ePersonDtoModel2);
|
component.addMemberToGroup(EPersonMock2);
|
||||||
|
|
||||||
expect(component.selectedReviewers).toEqual([ePersonDtoModel1, ePersonDtoModel2]);
|
expect(component.selectedReviewers).toEqual([EPersonMock, EPersonMock2]);
|
||||||
expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([ePersonDtoModel1.eperson, ePersonDtoModel2.eperson]);
|
expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([EPersonMock, EPersonMock2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should delete the member when present', () => {
|
it('should delete the member when present', () => {
|
||||||
spyOn(component.selectedReviewersUpdated, 'emit');
|
spyOn(component.selectedReviewersUpdated, 'emit');
|
||||||
ePersonDtoModel1.memberOfGroup = true;
|
component.selectedReviewers = [EPersonMock];
|
||||||
component.selectedReviewers = [ePersonDtoModel1];
|
|
||||||
|
|
||||||
component.deleteMemberFromGroup(ePersonDtoModel1);
|
component.deleteMemberFromGroup(EPersonMock);
|
||||||
|
|
||||||
expect(component.selectedReviewers).toEqual([]);
|
expect(component.selectedReviewers).toEqual([]);
|
||||||
expect(ePersonDtoModel1.memberOfGroup).toBeFalse();
|
|
||||||
expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([]);
|
expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -8,10 +8,7 @@ import { NotificationsService } from '../../../../shared/notifications/notificat
|
|||||||
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
||||||
import { Group } from '../../../../core/eperson/models/group.model';
|
import { Group } from '../../../../core/eperson/models/group.model';
|
||||||
import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators';
|
import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators';
|
||||||
import { EpersonDtoModel } from '../../../../core/eperson/models/eperson-dto.model';
|
|
||||||
import { EPerson } from '../../../../core/eperson/models/eperson.model';
|
import { EPerson } from '../../../../core/eperson/models/eperson.model';
|
||||||
import { Observable, of as observableOf } from 'rxjs';
|
|
||||||
import { hasValue } from '../../../../shared/empty.util';
|
|
||||||
import { PaginatedList } from '../../../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../../../core/data/paginated-list.model';
|
||||||
import {
|
import {
|
||||||
MembersListComponent,
|
MembersListComponent,
|
||||||
@@ -24,8 +21,8 @@ import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
|
|||||||
*/
|
*/
|
||||||
enum SubKey {
|
enum SubKey {
|
||||||
ActiveGroup,
|
ActiveGroup,
|
||||||
MembersDTO,
|
Members,
|
||||||
SearchResultsDTO,
|
SearchResults,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,7 +47,7 @@ export class ReviewersListComponent extends MembersListComponent implements OnIn
|
|||||||
@Output()
|
@Output()
|
||||||
selectedReviewersUpdated: EventEmitter<EPerson[]> = new EventEmitter();
|
selectedReviewersUpdated: EventEmitter<EPerson[]> = new EventEmitter();
|
||||||
|
|
||||||
selectedReviewers: EpersonDtoModel[] = [];
|
selectedReviewers: EPerson[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected groupService: GroupDataService,
|
protected groupService: GroupDataService,
|
||||||
@@ -100,54 +97,40 @@ export class ReviewersListComponent extends MembersListComponent implements OnIn
|
|||||||
retrieveMembers(page: number): void {
|
retrieveMembers(page: number): void {
|
||||||
this.config.currentPage = page;
|
this.config.currentPage = page;
|
||||||
if (this.groupId === null) {
|
if (this.groupId === null) {
|
||||||
this.unsubFrom(SubKey.MembersDTO);
|
this.unsubFrom(SubKey.Members);
|
||||||
const paginatedListOfDTOs: PaginatedList<EpersonDtoModel> = new PaginatedList();
|
const paginatedListOfEPersons: PaginatedList<EPerson> = new PaginatedList();
|
||||||
paginatedListOfDTOs.page = this.selectedReviewers;
|
paginatedListOfEPersons.page = this.selectedReviewers;
|
||||||
this.ePeopleMembersOfGroupDtos.next(paginatedListOfDTOs);
|
this.ePeopleMembersOfGroup.next(paginatedListOfEPersons);
|
||||||
} else {
|
} else {
|
||||||
super.retrieveMembers(page);
|
super.retrieveMembers(page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether the given {@link possibleMember} is part of the {@link selectedReviewers}.
|
* Removes the {@link eperson} from the {@link selectedReviewers}
|
||||||
*
|
*
|
||||||
* @param possibleMember The {@link EPerson} that needs to be checked
|
* @param eperson The {@link EPerson} to remove
|
||||||
*/
|
*/
|
||||||
isMemberOfGroup(possibleMember: EPerson): Observable<boolean> {
|
deleteMemberFromGroup(eperson: EPerson) {
|
||||||
return observableOf(hasValue(this.selectedReviewers.find((reviewer: EpersonDtoModel) => reviewer.eperson.id === possibleMember.id)));
|
const index = this.selectedReviewers.indexOf(eperson);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the {@link ePerson} from the {@link selectedReviewers}
|
|
||||||
*
|
|
||||||
* @param ePerson The {@link EpersonDtoModel} containg the {@link EPerson} to remove
|
|
||||||
*/
|
|
||||||
deleteMemberFromGroup(ePerson: EpersonDtoModel) {
|
|
||||||
ePerson.memberOfGroup = false;
|
|
||||||
const index = this.selectedReviewers.indexOf(ePerson);
|
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
this.selectedReviewers.splice(index, 1);
|
this.selectedReviewers.splice(index, 1);
|
||||||
}
|
}
|
||||||
this.selectedReviewersUpdated.emit(this.selectedReviewers.map((ePersonDtoModel: EpersonDtoModel) => ePersonDtoModel.eperson));
|
this.selectedReviewersUpdated.emit(this.selectedReviewers);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the {@link ePerson} to the {@link selectedReviewers} (or replaces it when {@link multipleReviewers} is
|
* Adds the {@link eperson} to the {@link selectedReviewers} (or replaces it when {@link multipleReviewers} is
|
||||||
* `false`). Afterwards it will emit the list.
|
* `false`). Afterwards it will emit the list.
|
||||||
*
|
*
|
||||||
* @param ePerson The {@link EPerson} to add to the list
|
* @param eperson The {@link EPerson} to add to the list
|
||||||
*/
|
*/
|
||||||
addMemberToGroup(ePerson: EpersonDtoModel) {
|
addMemberToGroup(eperson: EPerson) {
|
||||||
ePerson.memberOfGroup = true;
|
|
||||||
if (!this.multipleReviewers) {
|
if (!this.multipleReviewers) {
|
||||||
for (const selectedReviewer of this.selectedReviewers) {
|
|
||||||
selectedReviewer.memberOfGroup = false;
|
|
||||||
}
|
|
||||||
this.selectedReviewers = [];
|
this.selectedReviewers = [];
|
||||||
}
|
}
|
||||||
this.selectedReviewers.push(ePerson);
|
this.selectedReviewers.push(eperson);
|
||||||
this.selectedReviewersUpdated.emit(this.selectedReviewers.map((epersonDtoModel: EpersonDtoModel) => epersonDtoModel.eperson));
|
this.selectedReviewersUpdated.emit(this.selectedReviewers);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -434,10 +434,6 @@
|
|||||||
|
|
||||||
"admin.access-control.groups.form.members-list.headMembers": "Current Members",
|
"admin.access-control.groups.form.members-list.headMembers": "Current Members",
|
||||||
|
|
||||||
"admin.access-control.groups.form.members-list.search.scope.metadata": "Metadata",
|
|
||||||
|
|
||||||
"admin.access-control.groups.form.members-list.search.scope.email": "E-mail (exact)",
|
|
||||||
|
|
||||||
"admin.access-control.groups.form.members-list.search.button": "Search",
|
"admin.access-control.groups.form.members-list.search.button": "Search",
|
||||||
|
|
||||||
"admin.access-control.groups.form.members-list.table.id": "ID",
|
"admin.access-control.groups.form.members-list.table.id": "ID",
|
||||||
@@ -494,8 +490,6 @@
|
|||||||
|
|
||||||
"admin.access-control.groups.form.subgroups-list.table.edit.buttons.add": "Add subgroup with name \"{{name}}\"",
|
"admin.access-control.groups.form.subgroups-list.table.edit.buttons.add": "Add subgroup with name \"{{name}}\"",
|
||||||
|
|
||||||
"admin.access-control.groups.form.subgroups-list.table.edit.currentGroup": "Current group",
|
|
||||||
|
|
||||||
"admin.access-control.groups.form.subgroups-list.notification.success.addSubgroup": "Successfully added subgroup: \"{{name}}\"",
|
"admin.access-control.groups.form.subgroups-list.notification.success.addSubgroup": "Successfully added subgroup: \"{{name}}\"",
|
||||||
|
|
||||||
"admin.access-control.groups.form.subgroups-list.notification.failure.addSubgroup": "Failed to add subgroup: \"{{name}}\"",
|
"admin.access-control.groups.form.subgroups-list.notification.failure.addSubgroup": "Failed to add subgroup: \"{{name}}\"",
|
||||||
@@ -632,10 +626,6 @@
|
|||||||
|
|
||||||
"advanced-workflow-action-select-reviewer.groups.form.reviewers-list.headMembers": "Current Members",
|
"advanced-workflow-action-select-reviewer.groups.form.reviewers-list.headMembers": "Current Members",
|
||||||
|
|
||||||
"advanced-workflow-action-select-reviewer.groups.form.reviewers-list.search.scope.metadata": "Metadata",
|
|
||||||
|
|
||||||
"advanced-workflow-action-select-reviewer.groups.form.reviewers-list.search.scope.email": "E-mail (exact)",
|
|
||||||
|
|
||||||
"advanced-workflow-action-select-reviewer.groups.form.reviewers-list.search.button": "Search",
|
"advanced-workflow-action-select-reviewer.groups.form.reviewers-list.search.button": "Search",
|
||||||
|
|
||||||
"advanced-workflow-action-select-reviewer.groups.form.reviewers-list.table.id": "ID",
|
"advanced-workflow-action-select-reviewer.groups.form.reviewers-list.table.id": "ID",
|
||||||
|
Reference in New Issue
Block a user