mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge pull request #613 from atmire/manage-groups-page
Access Control - Group admin page
This commit is contained in:
@@ -176,12 +176,12 @@
|
|||||||
|
|
||||||
"admin.access-control.epeople.search.head": "Search",
|
"admin.access-control.epeople.search.head": "Search",
|
||||||
|
|
||||||
"admin.access-control.epeople.search.scope.name": "Name",
|
"admin.access-control.epeople.button.see-all": "Browse All",
|
||||||
|
|
||||||
"admin.access-control.epeople.search.scope.email": "E-mail (exact)",
|
|
||||||
|
|
||||||
"admin.access-control.epeople.search.scope.metadata": "Metadata",
|
"admin.access-control.epeople.search.scope.metadata": "Metadata",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.search.scope.email": "E-mail (exact)",
|
||||||
|
|
||||||
"admin.access-control.epeople.search.button": "Search",
|
"admin.access-control.epeople.search.button": "Search",
|
||||||
|
|
||||||
"admin.access-control.epeople.button.add": "Add EPerson",
|
"admin.access-control.epeople.button.add": "Add EPerson",
|
||||||
@@ -190,13 +190,13 @@
|
|||||||
|
|
||||||
"admin.access-control.epeople.table.name": "Name",
|
"admin.access-control.epeople.table.name": "Name",
|
||||||
|
|
||||||
"admin.access-control.epeople.table.email": "E-mail",
|
"admin.access-control.epeople.table.email": "E-mail (exact)",
|
||||||
|
|
||||||
"admin.access-control.epeople.table.edit": "Edit",
|
"admin.access-control.epeople.table.edit": "Edit",
|
||||||
|
|
||||||
"item.access-control.epeople.table.edit.buttons.edit": "Edit",
|
"admin.access-control.epeople.table.edit.buttons.edit": "Edit \"{{name}}\"",
|
||||||
|
|
||||||
"item.access-control.epeople.table.edit.buttons.remove": "Remove",
|
"admin.access-control.epeople.table.edit.buttons.remove": "Delete \"{{name}}\"",
|
||||||
|
|
||||||
"admin.access-control.epeople.no-items": "No EPeople to show.",
|
"admin.access-control.epeople.no-items": "No EPeople to show.",
|
||||||
|
|
||||||
@@ -228,12 +228,149 @@
|
|||||||
|
|
||||||
"admin.access-control.epeople.form.notification.edited.failure": "Failed to edit EPerson \"{{name}}\"",
|
"admin.access-control.epeople.form.notification.edited.failure": "Failed to edit EPerson \"{{name}}\"",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.form.groupsEPersonIsMemberOf": "Member of these groups:",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.form.table.id": "ID",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.form.table.name": "Name",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.form.memberOfNoGroups": "This EPerson is not a member of any groups",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.form.goToGroups": "Add to groups",
|
||||||
|
|
||||||
"admin.access-control.epeople.notification.deleted.failure": "Failed to delete EPerson: \"{{name}}\"",
|
"admin.access-control.epeople.notification.deleted.failure": "Failed to delete EPerson: \"{{name}}\"",
|
||||||
|
|
||||||
"admin.access-control.epeople.notification.deleted.success": "Successfully deleted EPerson: \"{{name}}\"",
|
"admin.access-control.epeople.notification.deleted.success": "Successfully deleted EPerson: \"{{name}}\"",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"admin.access-control.groups.title": "DSpace Angular :: Groups",
|
||||||
|
|
||||||
|
"admin.access-control.groups.head": "Groups",
|
||||||
|
|
||||||
|
"admin.access-control.groups.button.add": "Add group",
|
||||||
|
|
||||||
|
"admin.access-control.groups.search.head": "Search groups",
|
||||||
|
|
||||||
|
"admin.access-control.groups.button.see-all": "Browse all",
|
||||||
|
|
||||||
|
"admin.access-control.groups.search.button": "Search",
|
||||||
|
|
||||||
|
"admin.access-control.groups.table.id": "ID",
|
||||||
|
|
||||||
|
"admin.access-control.groups.table.name": "Name",
|
||||||
|
|
||||||
|
"admin.access-control.groups.table.members": "Members",
|
||||||
|
|
||||||
|
"admin.access-control.groups.table.comcol": "Community / Collection",
|
||||||
|
|
||||||
|
"admin.access-control.groups.table.edit": "Edit",
|
||||||
|
|
||||||
|
"admin.access-control.groups.table.edit.buttons.edit": "Edit \"{{name}}\"",
|
||||||
|
|
||||||
|
"admin.access-control.groups.table.edit.buttons.remove": "Delete \"{{name}}\"",
|
||||||
|
|
||||||
|
"admin.access-control.groups.no-items": "No groups found with this in their name or this as UUID",
|
||||||
|
|
||||||
|
"admin.access-control.groups.notification.deleted.success": "Successfully deleted group \"{{name}}\"",
|
||||||
|
|
||||||
|
"admin.access-control.groups.notification.deleted.failure": "Failed to delete group \"{{name}}\"",
|
||||||
|
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.head.create": "Create group",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.head.edit": "Edit group",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.groupName": "Group name",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.groupDescription": "Description",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.notification.created.success": "Successfully created Group \"{{name}}\"",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.notification.created.failure": "Failed to create Group \"{{name}}\"",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.notification.created.failure.groupNameInUse": "Failed to create Group with name: \"{{name}}\", make sure the name is not already in use.",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.members-list.head": "EPeople",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.members-list.search.head": "Add EPeople",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.members-list.button.see-all": "Browse All",
|
||||||
|
|
||||||
|
"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.table.id": "ID",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.members-list.table.name": "Name",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.members-list.table.edit": "Remove / Add",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.members-list.table.edit.buttons.remove": "Remove member with name \"{{name}}\"",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.members-list.notification.success.addMember": "Successfully added member: \"{{name}}\"",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.members-list.notification.failure.addMember": "Failed to add member: \"{{name}}\"",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.members-list.notification.success.deleteMember": "Successfully deleted member: \"{{name}}\"",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.members-list.notification.failure.deleteMember": "Failed to delete member: \"{{name}}\"",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.members-list.table.edit.buttons.add": "Add member with name \"{{name}}\"",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.members-list.notification.failure.noActiveGroup": "No current active group, submit a name first.",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.members-list.no-members-yet": "No members in group yet, search and add.",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.members-list.no-items": "No EPeople found in that search",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.subgroups-list.head": "Groups",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.subgroups-list.search.head": "Add Subgroup",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.subgroups-list.button.see-all": "Browse All",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.subgroups-list.headSubgroups": "Current Subgroups",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.subgroups-list.search.button": "Search",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.subgroups-list.table.id": "ID",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.subgroups-list.table.name": "Name",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.subgroups-list.table.edit": "Remove / Add",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.subgroups-list.table.edit.buttons.remove": "Remove 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.failure.addSubgroup": "Failed to add subgroup: \"{{name}}\"",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.subgroups-list.notification.success.deleteSubgroup": "Successfully deleted subgroup: \"{{name}}\"",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.subgroups-list.notification.failure.deleteSubgroup": "Failed to delete subgroup: \"{{name}}\"",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.subgroups-list.notification.failure.noActiveGroup": "No current active group, submit a name first.",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.subgroups-list.notification.failure.subgroupToAddIsActiveGroup": "This is the current group, can't be added.",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.subgroups-list.no-items": "No groups found with this in their name or this as UUID",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.subgroups-list.no-subgroups-yet": "No subgroups in group yet.",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.return": "Return to groups",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"admin.search.breadcrumbs": "Administrative Search",
|
"admin.search.breadcrumbs": "Administrative Search",
|
||||||
|
|
||||||
"admin.search.collection.edit": "Edit",
|
"admin.search.collection.edit": "Edit",
|
||||||
@@ -2326,6 +2463,9 @@
|
|||||||
|
|
||||||
"administrativeView.search.results.head": "Administrative Search",
|
"administrativeView.search.results.head": "Administrative Search",
|
||||||
|
|
||||||
|
"menu.section.admin_search": "Admin Search",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"uploader.browse": "browse",
|
"uploader.browse": "browse",
|
||||||
|
|
||||||
|
@@ -1,11 +1,24 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { EPeopleRegistryComponent } from './epeople-registry/epeople-registry.component';
|
import { EPeopleRegistryComponent } from './epeople-registry/epeople-registry.component';
|
||||||
|
import { GroupFormComponent } from './group-registry/group-form/group-form.component';
|
||||||
|
import { GroupsRegistryComponent } from './group-registry/groups-registry.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
RouterModule.forChild([
|
RouterModule.forChild([
|
||||||
{ path: 'epeople', component: EPeopleRegistryComponent, data: { title: 'admin.access-control.epeople.title' } },
|
{ path: 'epeople', component: EPeopleRegistryComponent, data: { title: 'admin.access-control.epeople.title' } },
|
||||||
|
{ path: 'groups', component: GroupsRegistryComponent, data: { title: 'admin.access-control.groups.title' } },
|
||||||
|
{
|
||||||
|
path: 'groups/:groupId',
|
||||||
|
component: GroupFormComponent,
|
||||||
|
data: {title: 'admin.registries.schema.title'}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'groups/newGroup',
|
||||||
|
component: GroupFormComponent,
|
||||||
|
data: {title: 'admin.registries.schema.title'}
|
||||||
|
},
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@@ -6,6 +6,10 @@ import { SharedModule } from '../../shared/shared.module';
|
|||||||
import { AdminAccessControlRoutingModule } from './admin-access-control-routing.module';
|
import { AdminAccessControlRoutingModule } from './admin-access-control-routing.module';
|
||||||
import { EPeopleRegistryComponent } from './epeople-registry/epeople-registry.component';
|
import { EPeopleRegistryComponent } from './epeople-registry/epeople-registry.component';
|
||||||
import { EPersonFormComponent } from './epeople-registry/eperson-form/eperson-form.component';
|
import { EPersonFormComponent } from './epeople-registry/eperson-form/eperson-form.component';
|
||||||
|
import { GroupFormComponent } from './group-registry/group-form/group-form.component';
|
||||||
|
import { MembersListComponent } from './group-registry/group-form/members-list/members-list.component';
|
||||||
|
import { SubgroupsListComponent } from './group-registry/group-form/subgroup-list/subgroups-list.component';
|
||||||
|
import { GroupsRegistryComponent } from './group-registry/groups-registry.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -17,7 +21,11 @@ import { EPersonFormComponent } from './epeople-registry/eperson-form/eperson-fo
|
|||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
EPeopleRegistryComponent,
|
EPeopleRegistryComponent,
|
||||||
EPersonFormComponent
|
EPersonFormComponent,
|
||||||
|
GroupsRegistryComponent,
|
||||||
|
GroupFormComponent,
|
||||||
|
SubgroupsListComponent,
|
||||||
|
MembersListComponent
|
||||||
],
|
],
|
||||||
entryComponents: []
|
entryComponents: []
|
||||||
})
|
})
|
||||||
|
@@ -15,7 +15,10 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 id="search" class="border-bottom pb-2">{{labelPrefix + 'search.head' | translate}}</h3>
|
<h3 id="search" class="border-bottom pb-2">{{labelPrefix + 'search.head' | translate}}
|
||||||
|
<button (click)="clearFormAndResetResult();"
|
||||||
|
class="btn btn-primary float-right">{{labelPrefix + 'button.see-all' | translate}}</button>
|
||||||
|
</h3>
|
||||||
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="row">
|
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="row">
|
||||||
<div class="col-12 col-sm-3">
|
<div class="col-12 col-sm-3">
|
||||||
<select name="scope" id="scope" formControlName="scope" class="form-control" aria-label="Search scope">
|
<select name="scope" id="scope" formControlName="scope" class="form-control" aria-label="Search scope">
|
||||||
@@ -64,12 +67,12 @@
|
|||||||
<div class="btn-group edit-field">
|
<div class="btn-group edit-field">
|
||||||
<button (click)="toggleEditEPerson(eperson)"
|
<button (click)="toggleEditEPerson(eperson)"
|
||||||
class="btn btn-outline-primary btn-sm access-control-editEPersonButton"
|
class="btn btn-outline-primary btn-sm access-control-editEPersonButton"
|
||||||
title="{{labelPrefix + 'table.edit.buttons.edit' | translate}}">
|
title="{{labelPrefix + 'table.edit.buttons.edit' | translate: {name: eperson.name} }}">
|
||||||
<i class="fas fa-edit fa-fw"></i>
|
<i class="fas fa-edit fa-fw"></i>
|
||||||
</button>
|
</button>
|
||||||
<button (click)="deleteEPerson(eperson)"
|
<button (click)="deleteEPerson(eperson)"
|
||||||
class="btn btn-outline-danger btn-sm access-control-deleteEPersonButton"
|
class="btn btn-outline-danger btn-sm access-control-deleteEPersonButton"
|
||||||
title="{{labelPrefix + 'table.edit.buttons.remove' | translate}}">
|
title="{{labelPrefix + 'table.edit.buttons.remove' | translate: {name: eperson.name} }}">
|
||||||
<i class="fas fa-trash-alt fa-fw"></i>
|
<i class="fas fa-trash-alt fa-fw"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { Router } from '@angular/router';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
@@ -20,6 +21,7 @@ import { getMockTranslateService } from '../../../shared/mocks/mock-translate.se
|
|||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
import { EPersonMock, EPersonMock2 } from '../../../shared/testing/eperson-mock';
|
import { EPersonMock, EPersonMock2 } from '../../../shared/testing/eperson-mock';
|
||||||
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
|
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
|
||||||
|
import { RouterStub } from '../../../shared/testing/router-stub';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
|
||||||
import { EPeopleRegistryComponent } from './epeople-registry.component';
|
import { EPeopleRegistryComponent } from './epeople-registry.component';
|
||||||
|
|
||||||
@@ -29,10 +31,11 @@ describe('EPeopleRegistryComponent', () => {
|
|||||||
let translateService: TranslateService;
|
let translateService: TranslateService;
|
||||||
let builderService: FormBuilderService;
|
let builderService: FormBuilderService;
|
||||||
|
|
||||||
const mockEPeople = [EPersonMock, EPersonMock2];
|
let mockEPeople;
|
||||||
let ePersonDataServiceStub: any;
|
let ePersonDataServiceStub: any;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
|
mockEPeople = [EPersonMock, EPersonMock2];
|
||||||
ePersonDataServiceStub = {
|
ePersonDataServiceStub = {
|
||||||
activeEPerson: null,
|
activeEPerson: null,
|
||||||
allEpeople: mockEPeople,
|
allEpeople: mockEPeople,
|
||||||
@@ -50,6 +53,9 @@ describe('EPeopleRegistryComponent', () => {
|
|||||||
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [result]));
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [result]));
|
||||||
}
|
}
|
||||||
if (scope === 'metadata') {
|
if (scope === 'metadata') {
|
||||||
|
if (query === '') {
|
||||||
|
return createSuccessfulRemoteDataObject$(new PaginatedList(null, this.allEpeople));
|
||||||
|
}
|
||||||
const result = this.allEpeople.find((ePerson: EPerson) => {
|
const result = this.allEpeople.find((ePerson: EPerson) => {
|
||||||
return (ePerson.name.includes(query) || ePerson.email.includes(query))
|
return (ePerson.name.includes(query) || ePerson.email.includes(query))
|
||||||
});
|
});
|
||||||
@@ -71,6 +77,9 @@ describe('EPeopleRegistryComponent', () => {
|
|||||||
},
|
},
|
||||||
clearEPersonRequests(): void {
|
clearEPersonRequests(): void {
|
||||||
// empty
|
// empty
|
||||||
|
},
|
||||||
|
getEPeoplePageRouterLink(): string {
|
||||||
|
return '/admin/access-control/epeople';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
builderService = getMockFormBuilderService();
|
builderService = getMockFormBuilderService();
|
||||||
@@ -89,7 +98,7 @@ describe('EPeopleRegistryComponent', () => {
|
|||||||
{ provide: EPersonDataService, useValue: ePersonDataServiceStub },
|
{ provide: EPersonDataService, useValue: ePersonDataServiceStub },
|
||||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
||||||
{ provide: FormBuilderService, useValue: builderService },
|
{ provide: FormBuilderService, useValue: builderService },
|
||||||
EPeopleRegistryComponent
|
{ provide: Router, useValue: new RouterStub() },
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { FormBuilder } from '@angular/forms';
|
import { FormBuilder } from '@angular/forms';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
import { Subscription } from 'rxjs/internal/Subscription';
|
||||||
import { map, take } from 'rxjs/operators';
|
import { map, take } from 'rxjs/operators';
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
@@ -19,7 +21,7 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio
|
|||||||
* A component used for managing all existing epeople within the repository.
|
* A component used for managing all existing epeople within the repository.
|
||||||
* The admin can create, edit or delete epeople here.
|
* The admin can create, edit or delete epeople here.
|
||||||
*/
|
*/
|
||||||
export class EPeopleRegistryComponent {
|
export class EPeopleRegistryComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
labelPrefix = 'admin.access-control.epeople.';
|
labelPrefix = 'admin.access-control.epeople.';
|
||||||
|
|
||||||
@@ -45,37 +47,45 @@ export class EPeopleRegistryComponent {
|
|||||||
// The search form
|
// The search form
|
||||||
searchForm;
|
searchForm;
|
||||||
|
|
||||||
|
// Current search in epersons registry
|
||||||
|
currentSearchQuery: string;
|
||||||
|
currentSearchScope: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of subscriptions
|
||||||
|
*/
|
||||||
|
subs: Subscription[] = [];
|
||||||
|
|
||||||
constructor(private epersonService: EPersonDataService,
|
constructor(private epersonService: EPersonDataService,
|
||||||
private translateService: TranslateService,
|
private translateService: TranslateService,
|
||||||
private notificationsService: NotificationsService,
|
private notificationsService: NotificationsService,
|
||||||
private formBuilder: FormBuilder) {
|
private formBuilder: FormBuilder,
|
||||||
this.updateEPeople({
|
private router: Router) {
|
||||||
currentPage: 1,
|
this.currentSearchQuery = '';
|
||||||
elementsPerPage: this.config.pageSize
|
this.currentSearchScope = 'metadata';
|
||||||
});
|
|
||||||
this.isEPersonFormShown = false;
|
|
||||||
this.searchForm = this.formBuilder.group(({
|
this.searchForm = this.formBuilder.group(({
|
||||||
scope: 'metadata',
|
scope: 'metadata',
|
||||||
query: '',
|
query: '',
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.isEPersonFormShown = false;
|
||||||
|
this.search({ scope: this.currentSearchScope, query: this.currentSearchQuery });
|
||||||
|
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
|
||||||
|
if (eperson != null && eperson.id) {
|
||||||
|
this.isEPersonFormShown = true;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event triggered when the user changes page
|
* Event triggered when the user changes page
|
||||||
* @param event
|
* @param event
|
||||||
*/
|
*/
|
||||||
onPageChange(event) {
|
onPageChange(event) {
|
||||||
this.updateEPeople({
|
this.config.currentPage = event;
|
||||||
currentPage: event,
|
this.search({ scope: this.currentSearchScope, query: this.currentSearchQuery })
|
||||||
elementsPerPage: this.config.pageSize
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the list of EPeople by fetching it from the rest api or cache
|
|
||||||
*/
|
|
||||||
private updateEPeople(options) {
|
|
||||||
this.ePeople = this.epersonService.getEPeople(options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -93,8 +103,20 @@ export class EPeopleRegistryComponent {
|
|||||||
* @param data Contains scope and query param
|
* @param data Contains scope and query param
|
||||||
*/
|
*/
|
||||||
search(data: any) {
|
search(data: any) {
|
||||||
this.ePeople = this.epersonService.searchByScope(data.scope, data.query, {
|
const query: string = data.query;
|
||||||
currentPage: 1,
|
const scope: string = data.scope;
|
||||||
|
if (query != null && this.currentSearchQuery !== query) {
|
||||||
|
this.router.navigateByUrl(this.epersonService.getEPeoplePageRouterLink());
|
||||||
|
this.currentSearchQuery = query;
|
||||||
|
this.config.currentPage = 1;
|
||||||
|
}
|
||||||
|
if (scope != null && this.currentSearchScope !== scope) {
|
||||||
|
this.router.navigateByUrl(this.epersonService.getEPeoplePageRouterLink());
|
||||||
|
this.currentSearchScope = scope;
|
||||||
|
this.config.currentPage = 1;
|
||||||
|
}
|
||||||
|
this.ePeople = this.epersonService.searchByScope(this.currentSearchScope, this.currentSearchQuery, {
|
||||||
|
currentPage: this.config.currentPage,
|
||||||
elementsPerPage: this.config.pageSize
|
elementsPerPage: this.config.pageSize
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -151,6 +173,13 @@ export class EPeopleRegistryComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsub all subscriptions
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||||
|
}
|
||||||
|
|
||||||
scrollToTop() {
|
scrollToTop() {
|
||||||
(function smoothscroll() {
|
(function smoothscroll() {
|
||||||
const currentScroll = document.documentElement.scrollTop || document.body.scrollTop;
|
const currentScroll = document.documentElement.scrollTop || document.body.scrollTop;
|
||||||
@@ -160,4 +189,14 @@ export class EPeopleRegistryComponent {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset all input-fields to be empty and search all search
|
||||||
|
*/
|
||||||
|
clearFormAndResetResult() {
|
||||||
|
this.searchForm.patchValue({
|
||||||
|
query: '',
|
||||||
|
});
|
||||||
|
this.search({ query: '' });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,6 @@ const initialState: EPeopleRegistryState = {
|
|||||||
* @param action The EPeopleRegistryAction to perform on the state
|
* @param action The EPeopleRegistryAction to perform on the state
|
||||||
*/
|
*/
|
||||||
export function ePeopleRegistryReducer(state = initialState, action: EPeopleRegistryAction): EPeopleRegistryState {
|
export function ePeopleRegistryReducer(state = initialState, action: EPeopleRegistryAction): EPeopleRegistryState {
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
|
||||||
case EPeopleRegistryActionTypes.EDIT_EPERSON: {
|
case EPeopleRegistryActionTypes.EDIT_EPERSON: {
|
||||||
|
@@ -15,3 +15,44 @@
|
|||||||
(cancel)="onCancel()"
|
(cancel)="onCancel()"
|
||||||
(submitForm)="onSubmit()">
|
(submitForm)="onSubmit()">
|
||||||
</ds-form>
|
</ds-form>
|
||||||
|
|
||||||
|
<div *ngIf="epersonService.getActiveEPerson() | async">
|
||||||
|
<h5>{{messagePrefix + '.groupsEPersonIsMemberOf' | translate}}</h5>
|
||||||
|
|
||||||
|
<ds-pagination
|
||||||
|
*ngIf="(groups | async)?.payload?.totalElements > 0"
|
||||||
|
[paginationOptions]="config"
|
||||||
|
[pageInfoState]="(groups | async)?.payload"
|
||||||
|
[collectionSize]="(groups | async)?.payload?.totalElements"
|
||||||
|
[hideGear]="true"
|
||||||
|
[hidePagerWhenSinglePage]="true"
|
||||||
|
(pageChange)="onPageChange($event)">
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="groups" class="table table-striped table-hover table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">{{messagePrefix + '.table.id' | translate}}</th>
|
||||||
|
<th scope="col">{{messagePrefix + '.table.name' | translate}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let group of (groups | async)?.payload?.page">
|
||||||
|
<td>{{group.id}}</td>
|
||||||
|
<td><a (click)="groupsDataService.startEditingNewGroup(group)"
|
||||||
|
[routerLink]="[groupsDataService.getGroupEditPageRouterLink(group)]">{{group.name}}</a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</ds-pagination>
|
||||||
|
|
||||||
|
<div *ngIf="(groups | async)?.payload?.totalElements == 0" class="alert alert-info w-100 mb-2" role="alert">
|
||||||
|
<div>{{messagePrefix + '.memberOfNoGroups' | translate}}</div>
|
||||||
|
<div>
|
||||||
|
<button [routerLink]="[groupsDataService.getGroupRegistryRouterLink()]"
|
||||||
|
class="btn btn-primary">{{messagePrefix + '.goToGroups' | translate}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
@@ -7,13 +9,18 @@ import { BrowserModule } from '@angular/platform-browser';
|
|||||||
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 } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
|
||||||
|
import { ObjectCacheService } from '../../../../core/cache/object-cache.service';
|
||||||
import { RestResponse } from '../../../../core/cache/response.models';
|
import { RestResponse } from '../../../../core/cache/response.models';
|
||||||
|
import { DSOChangeAnalyzer } from '../../../../core/data/dso-change-analyzer.service';
|
||||||
import { PaginatedList } from '../../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
import { FindListOptions } from '../../../../core/data/request.models';
|
import { FindListOptions } from '../../../../core/data/request.models';
|
||||||
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
|
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
|
||||||
import { EPerson } from '../../../../core/eperson/models/eperson.model';
|
import { EPerson } from '../../../../core/eperson/models/eperson.model';
|
||||||
|
import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service';
|
||||||
import { PageInfo } from '../../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||||
|
import { UUIDService } from '../../../../core/shared/uuid.service';
|
||||||
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
|
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
|
||||||
import { getMockFormBuilderService } from '../../../../shared/mocks/mock-form-builder-service';
|
import { getMockFormBuilderService } from '../../../../shared/mocks/mock-form-builder-service';
|
||||||
import { getMockTranslateService } from '../../../../shared/mocks/mock-translate.service';
|
import { getMockTranslateService } from '../../../../shared/mocks/mock-translate.service';
|
||||||
@@ -31,10 +38,11 @@ describe('EPersonFormComponent', () => {
|
|||||||
let translateService: TranslateService;
|
let translateService: TranslateService;
|
||||||
let builderService: FormBuilderService;
|
let builderService: FormBuilderService;
|
||||||
|
|
||||||
const mockEPeople = [EPersonMock, EPersonMock2];
|
let mockEPeople;
|
||||||
let ePersonDataServiceStub: any;
|
let ePersonDataServiceStub: any;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
|
mockEPeople = [EPersonMock, EPersonMock2];
|
||||||
ePersonDataServiceStub = {
|
ePersonDataServiceStub = {
|
||||||
activeEPerson: null,
|
activeEPerson: null,
|
||||||
allEpeople: mockEPeople,
|
allEpeople: mockEPeople,
|
||||||
@@ -52,6 +60,9 @@ describe('EPersonFormComponent', () => {
|
|||||||
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [result]));
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [result]));
|
||||||
}
|
}
|
||||||
if (scope === 'metadata') {
|
if (scope === 'metadata') {
|
||||||
|
if (query === '') {
|
||||||
|
return createSuccessfulRemoteDataObject$(new PaginatedList(null, this.allEpeople));
|
||||||
|
}
|
||||||
const result = this.allEpeople.find((ePerson: EPerson) => {
|
const result = this.allEpeople.find((ePerson: EPerson) => {
|
||||||
return (ePerson.name.includes(query) || ePerson.email.includes(query))
|
return (ePerson.name.includes(query) || ePerson.email.includes(query))
|
||||||
});
|
});
|
||||||
@@ -107,6 +118,13 @@ describe('EPersonFormComponent', () => {
|
|||||||
{ provide: EPersonDataService, useValue: ePersonDataServiceStub },
|
{ provide: EPersonDataService, useValue: ePersonDataServiceStub },
|
||||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
||||||
{ provide: FormBuilderService, useValue: builderService },
|
{ provide: FormBuilderService, useValue: builderService },
|
||||||
|
{ provide: DSOChangeAnalyzer, useValue: {} },
|
||||||
|
{ provide: HttpClient, useValue: {} },
|
||||||
|
{ provide: ObjectCacheService, useValue: {} },
|
||||||
|
{ provide: UUIDService, useValue: {} },
|
||||||
|
{ provide: Store, useValue: {} },
|
||||||
|
{ provide: RemoteDataBuildService, useValue: {} },
|
||||||
|
{ provide: HALEndpointService, useValue: {} },
|
||||||
EPeopleRegistryComponent
|
EPeopleRegistryComponent
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
@@ -116,7 +134,6 @@ describe('EPersonFormComponent', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(EPersonFormComponent);
|
fixture = TestBed.createComponent(EPersonFormComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
component.ngOnInit();
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -125,13 +142,21 @@ describe('EPersonFormComponent', () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
describe('when submitting the form', () => {
|
describe('when submitting the form', () => {
|
||||||
const firstName = 'testName';
|
let firstName;
|
||||||
const lastName = 'testLastName';
|
let lastName;
|
||||||
const email = 'testEmail@test.com';
|
let email;
|
||||||
const canLogIn = false;
|
let canLogIn;
|
||||||
const requireCertificate = false;
|
let requireCertificate;
|
||||||
|
|
||||||
const expected = Object.assign(new EPerson(), {
|
let expected;
|
||||||
|
beforeEach(() => {
|
||||||
|
firstName = 'testName';
|
||||||
|
lastName = 'testLastName';
|
||||||
|
email = 'testEmail@test.com';
|
||||||
|
canLogIn = false;
|
||||||
|
requireCertificate = false;
|
||||||
|
|
||||||
|
expected = Object.assign(new EPerson(), {
|
||||||
metadata: {
|
metadata: {
|
||||||
'eperson.firstname': [
|
'eperson.firstname': [
|
||||||
{
|
{
|
||||||
@@ -148,7 +173,6 @@ describe('EPersonFormComponent', () => {
|
|||||||
canLogIn: canLogIn,
|
canLogIn: canLogIn,
|
||||||
requireCertificate: requireCertificate,
|
requireCertificate: requireCertificate,
|
||||||
});
|
});
|
||||||
beforeEach(() => {
|
|
||||||
spyOn(component.submitForm, 'emit');
|
spyOn(component.submitForm, 'emit');
|
||||||
component.firstName.value = firstName;
|
component.firstName.value = firstName;
|
||||||
component.lastName.value = lastName;
|
component.lastName.value = lastName;
|
||||||
@@ -171,7 +195,10 @@ describe('EPersonFormComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('with an active eperson', () => {
|
describe('with an active eperson', () => {
|
||||||
const expectedWithId = Object.assign(new EPerson(), {
|
let expectedWithId;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
expectedWithId = Object.assign(new EPerson(), {
|
||||||
metadata: {
|
metadata: {
|
||||||
'eperson.firstname': [
|
'eperson.firstname': [
|
||||||
{
|
{
|
||||||
@@ -188,8 +215,6 @@ describe('EPersonFormComponent', () => {
|
|||||||
canLogIn: canLogIn,
|
canLogIn: canLogIn,
|
||||||
requireCertificate: requireCertificate,
|
requireCertificate: requireCertificate,
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
spyOn(ePersonDataServiceStub, 'getActiveEPerson').and.returnValue(observableOf(expectedWithId));
|
spyOn(ePersonDataServiceStub, 'getActiveEPerson').and.returnValue(observableOf(expectedWithId));
|
||||||
component.onSubmit();
|
component.onSubmit();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
@@ -7,17 +7,21 @@ import {
|
|||||||
DynamicInputModel
|
DynamicInputModel
|
||||||
} from '@ng-dynamic-forms/core';
|
} from '@ng-dynamic-forms/core';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { combineLatest } from 'rxjs/internal/observable/combineLatest';
|
import { Subscription, combineLatest } from 'rxjs';
|
||||||
import { Subscription } from 'rxjs/internal/Subscription';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { take } from 'rxjs/operators';
|
import { take } from 'rxjs/operators';
|
||||||
import { RestResponse } from '../../../../core/cache/response.models';
|
import { RestResponse } from '../../../../core/cache/response.models';
|
||||||
import { PaginatedList } from '../../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
|
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 { EPerson } from '../../../../core/eperson/models/eperson.model';
|
import { EPerson } from '../../../../core/eperson/models/eperson.model';
|
||||||
|
import { Group } from '../../../../core/eperson/models/group.model';
|
||||||
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators';
|
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators';
|
||||||
import { hasValue } from '../../../../shared/empty.util';
|
import { hasValue } from '../../../../shared/empty.util';
|
||||||
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 { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-eperson-form',
|
selector: 'ds-eperson-form',
|
||||||
@@ -106,12 +110,27 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
subs: Subscription[] = [];
|
subs: Subscription[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of all the groups this EPerson is a member of
|
||||||
|
*/
|
||||||
|
groups: Observable<RemoteData<PaginatedList<Group>>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pagination config used to display the list of groups
|
||||||
|
*/
|
||||||
|
config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
|
||||||
|
id: 'groups-ePersonMemberOf-list-pagination',
|
||||||
|
pageSize: 5,
|
||||||
|
currentPage: 1
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to retrieve initial active eperson, to fill in checkboxes at component creation
|
* Try to retrieve initial active eperson, to fill in checkboxes at component creation
|
||||||
*/
|
*/
|
||||||
epersonInitial: EPerson;
|
epersonInitial: EPerson;
|
||||||
|
|
||||||
constructor(public epersonService: EPersonDataService,
|
constructor(public epersonService: EPersonDataService,
|
||||||
|
public groupsDataService: GroupDataService,
|
||||||
private formBuilderService: FormBuilderService,
|
private formBuilderService: FormBuilderService,
|
||||||
private translateService: TranslateService,
|
private translateService: TranslateService,
|
||||||
private notificationsService: NotificationsService,) {
|
private notificationsService: NotificationsService,) {
|
||||||
@@ -181,6 +200,12 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
];
|
];
|
||||||
this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
|
this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
|
||||||
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
|
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
|
||||||
|
if (eperson != null) {
|
||||||
|
this.groups = this.groupsDataService.findAllByHref(eperson._links.groups.href, {
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: this.config.pageSize
|
||||||
|
});
|
||||||
|
}
|
||||||
this.formGroup.patchValue({
|
this.formGroup.patchValue({
|
||||||
firstName: eperson != null ? eperson.firstMetadataValue('eperson.firstname') : '',
|
firstName: eperson != null ? eperson.firstMetadataValue('eperson.firstname') : '',
|
||||||
lastName: eperson != null ? eperson.firstMetadataValue('eperson.lastname') : '',
|
lastName: eperson != null ? eperson.firstMetadataValue('eperson.lastname') : '',
|
||||||
@@ -209,7 +234,6 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
onSubmit() {
|
onSubmit() {
|
||||||
this.epersonService.getActiveEPerson().pipe(take(1)).subscribe(
|
this.epersonService.getActiveEPerson().pipe(take(1)).subscribe(
|
||||||
(ePerson: EPerson) => {
|
(ePerson: EPerson) => {
|
||||||
console.log('onsubmit ep', ePerson)
|
|
||||||
const values = {
|
const values = {
|
||||||
metadata: {
|
metadata: {
|
||||||
'eperson.firstname': [
|
'eperson.firstname': [
|
||||||
@@ -241,7 +265,6 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
* @param values
|
* @param values
|
||||||
*/
|
*/
|
||||||
createNewEPerson(values) {
|
createNewEPerson(values) {
|
||||||
console.log('createNewEPerson(values)', values)
|
|
||||||
const ePersonToCreate = Object.assign(new EPerson(), values);
|
const ePersonToCreate = Object.assign(new EPerson(), values);
|
||||||
|
|
||||||
const response = this.epersonService.tryToCreate(ePersonToCreate);
|
const response = this.epersonService.tryToCreate(ePersonToCreate);
|
||||||
@@ -322,18 +345,25 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset all input-fields to be empty
|
* Event triggered when the user changes page
|
||||||
|
* @param event
|
||||||
*/
|
*/
|
||||||
clearFields() {
|
onPageChange(event) {
|
||||||
this.formGroup.patchValue({
|
this.updateGroups({
|
||||||
firstName: '',
|
currentPage: event,
|
||||||
lastName: '',
|
elementsPerPage: this.config.pageSize
|
||||||
email: '',
|
|
||||||
canLogin: true,
|
|
||||||
requireCertificate: false
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the list of groups by fetching it from the rest api or cache
|
||||||
|
*/
|
||||||
|
private updateGroups(options) {
|
||||||
|
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
|
||||||
|
this.groups = this.groupsDataService.findAllByHref(eperson._links.groups.href, options);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancel the current edit when component is destroyed & unsub all subscriptions
|
* Cancel the current edit when component is destroyed & unsub all subscriptions
|
||||||
*/
|
*/
|
||||||
|
@@ -0,0 +1,33 @@
|
|||||||
|
<div class="container">
|
||||||
|
<div class="group-form row">
|
||||||
|
<div class="col-12">
|
||||||
|
|
||||||
|
<div *ngIf="groupDataService.getActiveGroup() | async; then editheader; else createHeader"></div>
|
||||||
|
|
||||||
|
<ng-template #createHeader>
|
||||||
|
<h2 class="border-bottom pb-2">{{messagePrefix + '.head.create' | translate}}</h2>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #editheader>
|
||||||
|
<h2 class="border-bottom pb-2">{{messagePrefix + '.head.edit' | translate}}</h2>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ds-form [formId]="formId"
|
||||||
|
[formModel]="formModel"
|
||||||
|
[formGroup]="formGroup"
|
||||||
|
[formLayout]="formLayout"
|
||||||
|
(cancel)="onCancel()"
|
||||||
|
(submitForm)="onSubmit()">
|
||||||
|
</ds-form>
|
||||||
|
|
||||||
|
<ds-members-list *ngIf="groupBeingEdited != null" [messagePrefix]="messagePrefix + '.members-list'"></ds-members-list>
|
||||||
|
<ds-subgroups-list *ngIf="groupBeingEdited != null" [messagePrefix]="messagePrefix + '.subgroups-list'"></ds-subgroups-list>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button [routerLink]="[this.groupDataService.getGroupRegistryRouterLink()]"
|
||||||
|
class="btn btn-primary">{{messagePrefix + '.return' | translate}}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,153 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
|
||||||
|
import { ObjectCacheService } from '../../../../core/cache/object-cache.service';
|
||||||
|
import { RestResponse } from '../../../../core/cache/response.models';
|
||||||
|
import { DSOChangeAnalyzer } from '../../../../core/data/dso-change-analyzer.service';
|
||||||
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
|
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
|
||||||
|
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
||||||
|
import { Group } from '../../../../core/eperson/models/group.model';
|
||||||
|
import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service';
|
||||||
|
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||||
|
import { UUIDService } from '../../../../core/shared/uuid.service';
|
||||||
|
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
|
||||||
|
import { getMockFormBuilderService } from '../../../../shared/mocks/mock-form-builder-service';
|
||||||
|
import { MockRouter } from '../../../../shared/mocks/mock-router';
|
||||||
|
import { getMockTranslateService } from '../../../../shared/mocks/mock-translate.service';
|
||||||
|
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||||
|
import { GroupMock, GroupMock2 } from '../../../../shared/testing/group-mock';
|
||||||
|
import { MockTranslateLoader } from '../../../../shared/testing/mock-translate-loader';
|
||||||
|
import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service-stub';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
|
||||||
|
import { GroupFormComponent } from './group-form.component';
|
||||||
|
|
||||||
|
describe('GroupFormComponent', () => {
|
||||||
|
let component: GroupFormComponent;
|
||||||
|
let fixture: ComponentFixture<GroupFormComponent>;
|
||||||
|
let translateService: TranslateService;
|
||||||
|
let builderService: FormBuilderService;
|
||||||
|
let ePersonDataServiceStub: any;
|
||||||
|
let groupsDataServiceStub: any;
|
||||||
|
let router;
|
||||||
|
|
||||||
|
let groups;
|
||||||
|
let groupName;
|
||||||
|
let groupDescription;
|
||||||
|
let expected;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
groups = [GroupMock, GroupMock2]
|
||||||
|
groupName = 'testGroupName';
|
||||||
|
groupDescription = 'testDescription';
|
||||||
|
expected = Object.assign(new Group(), {
|
||||||
|
name: groupName,
|
||||||
|
metadata: {
|
||||||
|
'dc.description': [
|
||||||
|
{
|
||||||
|
value: groupDescription
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
ePersonDataServiceStub = {};
|
||||||
|
groupsDataServiceStub = {
|
||||||
|
allGroups: groups,
|
||||||
|
activeGroup: null,
|
||||||
|
getActiveGroup(): Observable<Group> {
|
||||||
|
return observableOf(this.activeGroup);
|
||||||
|
},
|
||||||
|
getGroupRegistryRouterLink(): string {
|
||||||
|
return '/admin/access-control/groups';
|
||||||
|
},
|
||||||
|
editGroup(group: Group) {
|
||||||
|
this.activeGroup = group
|
||||||
|
},
|
||||||
|
cancelEditGroup(): void {
|
||||||
|
this.activeGroup = null;
|
||||||
|
},
|
||||||
|
findById(id: string) {
|
||||||
|
return observableOf({ payload: null, hasSucceeded: true });
|
||||||
|
},
|
||||||
|
tryToCreate(group: Group): Observable<RestResponse> {
|
||||||
|
this.allGroups = [...this.allGroups, group]
|
||||||
|
return observableOf(new RestResponse(true, 200, 'Success'));
|
||||||
|
},
|
||||||
|
searchGroups(query: string): Observable<RemoteData<PaginatedList<Group>>> {
|
||||||
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), []))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
builderService = getMockFormBuilderService();
|
||||||
|
translateService = getMockTranslateService();
|
||||||
|
router = new MockRouter();
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
|
||||||
|
TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: MockTranslateLoader
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
declarations: [GroupFormComponent],
|
||||||
|
providers: [GroupFormComponent,
|
||||||
|
{ provide: EPersonDataService, useValue: ePersonDataServiceStub },
|
||||||
|
{ provide: GroupDataService, useValue: groupsDataServiceStub },
|
||||||
|
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
||||||
|
{ provide: FormBuilderService, useValue: builderService },
|
||||||
|
{ provide: DSOChangeAnalyzer, useValue: {} },
|
||||||
|
{ provide: HttpClient, useValue: {} },
|
||||||
|
{ provide: ObjectCacheService, useValue: {} },
|
||||||
|
{ provide: UUIDService, useValue: {} },
|
||||||
|
{ provide: Store, useValue: {} },
|
||||||
|
{ provide: RemoteDataBuildService, useValue: {} },
|
||||||
|
{ provide: HALEndpointService, useValue: {} },
|
||||||
|
{ provide: ActivatedRoute, useValue: { data: observableOf({ dso: { payload: {} } }), params: observableOf({}) } },
|
||||||
|
{ provide: Router, useValue: router },
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(GroupFormComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create GroupFormComponent', inject([GroupFormComponent], (comp: GroupFormComponent) => {
|
||||||
|
expect(comp).toBeDefined();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('when submitting the form', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(component.submitForm, 'emit');
|
||||||
|
component.groupName.value = groupName;
|
||||||
|
component.groupDescription.value = groupDescription;
|
||||||
|
});
|
||||||
|
describe('without active Group', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
component.onSubmit();
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit a new group using the correct values', async(() => {
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
expect(component.submitForm.emit).toHaveBeenCalledWith(expected);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,280 @@
|
|||||||
|
import { Component, EventEmitter, HostListener, OnDestroy, OnInit, Output } from '@angular/core';
|
||||||
|
import { FormGroup } from '@angular/forms';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import {
|
||||||
|
DynamicFormControlModel,
|
||||||
|
DynamicFormLayout,
|
||||||
|
DynamicInputModel,
|
||||||
|
DynamicTextAreaModel
|
||||||
|
} from '@ng-dynamic-forms/core';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { combineLatest } from 'rxjs/internal/observable/combineLatest';
|
||||||
|
import { Subscription } from 'rxjs/internal/Subscription';
|
||||||
|
import { take } from 'rxjs/operators';
|
||||||
|
import { RestResponse } from '../../../../core/cache/response.models';
|
||||||
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
|
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
|
||||||
|
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
||||||
|
import { Group } from '../../../../core/eperson/models/group.model';
|
||||||
|
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators';
|
||||||
|
import { hasValue, isNotEmpty } from '../../../../shared/empty.util';
|
||||||
|
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
|
||||||
|
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-group-form',
|
||||||
|
templateUrl: './group-form.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* A form used for creating and editing groups
|
||||||
|
*/
|
||||||
|
export class GroupFormComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
messagePrefix = 'admin.access-control.groups.form';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A unique id used for ds-form
|
||||||
|
*/
|
||||||
|
formId = 'group-form';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamic models for the inputs of form
|
||||||
|
*/
|
||||||
|
groupName: DynamicInputModel;
|
||||||
|
groupDescription: DynamicTextAreaModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of all dynamic input models
|
||||||
|
*/
|
||||||
|
formModel: DynamicFormControlModel[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layout used for structuring the form inputs
|
||||||
|
*/
|
||||||
|
formLayout: DynamicFormLayout = {
|
||||||
|
groupName: {
|
||||||
|
grid: {
|
||||||
|
host: 'row'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
groupDescription: {
|
||||||
|
grid: {
|
||||||
|
host: 'row'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A FormGroup that combines all inputs
|
||||||
|
*/
|
||||||
|
formGroup: FormGroup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An EventEmitter that's fired whenever the form is being submitted
|
||||||
|
*/
|
||||||
|
@Output() submitForm: EventEmitter<any> = new EventEmitter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An EventEmitter that's fired whenever the form is cancelled
|
||||||
|
*/
|
||||||
|
@Output() cancelForm: EventEmitter<any> = new EventEmitter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of subscriptions
|
||||||
|
*/
|
||||||
|
subs: Subscription[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group currently being edited
|
||||||
|
*/
|
||||||
|
groupBeingEdited: Group;
|
||||||
|
|
||||||
|
constructor(public groupDataService: GroupDataService,
|
||||||
|
private ePersonDataService: EPersonDataService,
|
||||||
|
private formBuilderService: FormBuilderService,
|
||||||
|
private translateService: TranslateService,
|
||||||
|
private notificationsService: NotificationsService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
protected router: Router) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.subs.push(this.route.params.subscribe((params) => {
|
||||||
|
this.setActiveGroup(params.groupId)
|
||||||
|
}));
|
||||||
|
combineLatest(
|
||||||
|
this.translateService.get(`${this.messagePrefix}.groupName`),
|
||||||
|
this.translateService.get(`${this.messagePrefix}.groupDescription`),
|
||||||
|
).subscribe(([groupName, groupDescription]) => {
|
||||||
|
this.groupName = new DynamicInputModel({
|
||||||
|
id: 'groupName',
|
||||||
|
label: groupName,
|
||||||
|
name: 'groupName',
|
||||||
|
validators: {
|
||||||
|
required: null,
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
this.groupDescription = new DynamicTextAreaModel({
|
||||||
|
id: 'groupDescription',
|
||||||
|
label: groupDescription,
|
||||||
|
name: 'groupDescription',
|
||||||
|
required: false,
|
||||||
|
});
|
||||||
|
this.formModel = [
|
||||||
|
this.groupName,
|
||||||
|
this.groupDescription
|
||||||
|
];
|
||||||
|
this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
|
||||||
|
this.subs.push(this.groupDataService.getActiveGroup().subscribe((activeGroup: Group) => {
|
||||||
|
if (activeGroup != null) {
|
||||||
|
this.groupBeingEdited = activeGroup;
|
||||||
|
this.formGroup.patchValue({
|
||||||
|
groupName: activeGroup != null ? activeGroup.name : '',
|
||||||
|
groupDescription: activeGroup != null ? activeGroup.firstMetadataValue('dc.description') : '',
|
||||||
|
});
|
||||||
|
if (activeGroup.permanent) {
|
||||||
|
this.formGroup.get('groupName').disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop editing the currently selected group
|
||||||
|
*/
|
||||||
|
onCancel() {
|
||||||
|
this.groupDataService.cancelEditGroup();
|
||||||
|
this.cancelForm.emit();
|
||||||
|
this.router.navigate([this.groupDataService.getGroupRegistryRouterLink()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit the form
|
||||||
|
* When the eperson has an id attached -> Edit the eperson
|
||||||
|
* When the eperson has no id attached -> Create new eperson
|
||||||
|
* Emit the updated/created eperson using the EventEmitter submitForm
|
||||||
|
*/
|
||||||
|
onSubmit() {
|
||||||
|
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe(
|
||||||
|
(group: Group) => {
|
||||||
|
const values = {
|
||||||
|
name: this.groupName.value,
|
||||||
|
metadata: {
|
||||||
|
'dc.description': [
|
||||||
|
{
|
||||||
|
value: this.groupDescription.value
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (group === null) {
|
||||||
|
this.createNewGroup(values);
|
||||||
|
} else {
|
||||||
|
this.editGroup(group, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new Group based on given values from form
|
||||||
|
* @param values
|
||||||
|
*/
|
||||||
|
createNewGroup(values) {
|
||||||
|
const groupToCreate = Object.assign(new Group(), values);
|
||||||
|
const response = this.groupDataService.tryToCreate(groupToCreate);
|
||||||
|
response.pipe(take(1)).subscribe((restResponse: RestResponse) => {
|
||||||
|
if (restResponse.isSuccessful) {
|
||||||
|
this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.created.success', { name: groupToCreate.name }));
|
||||||
|
this.submitForm.emit(groupToCreate);
|
||||||
|
const resp: any = restResponse;
|
||||||
|
if (isNotEmpty(resp.resourceSelfLinks)) {
|
||||||
|
const groupSelfLink = resp.resourceSelfLinks[0];
|
||||||
|
this.setActiveGroupWithLink(groupSelfLink);
|
||||||
|
this.router.navigateByUrl(this.groupDataService.getGroupEditPageRouterLinkWithID(this.groupDataService.getUUIDFromString(groupSelfLink)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.created.failure', { name: groupToCreate.name }));
|
||||||
|
this.showNotificationIfNameInUse(groupToCreate, 'created');
|
||||||
|
this.cancelForm.emit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for the given group if there is already a group in the system with that group name and shows error if that
|
||||||
|
* is the case
|
||||||
|
* @param group group to check
|
||||||
|
* @param notificationSection whether in create or edit
|
||||||
|
*/
|
||||||
|
private showNotificationIfNameInUse(group: Group, notificationSection: string) {
|
||||||
|
// Relevant message for group name in use
|
||||||
|
this.subs.push(this.groupDataService.searchGroups(group.name, {
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: 0
|
||||||
|
}).pipe(getSucceededRemoteData(), getRemoteDataPayload())
|
||||||
|
.subscribe((list: PaginatedList<Group>) => {
|
||||||
|
if (list.totalElements > 0) {
|
||||||
|
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.' + notificationSection + '.failure.groupNameInUse', {
|
||||||
|
name: group.name
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* // TODO
|
||||||
|
* @param group
|
||||||
|
* @param values
|
||||||
|
*/
|
||||||
|
editGroup(group: Group, values) {
|
||||||
|
// TODO (backend)
|
||||||
|
console.log('TODO implement editGroup', values);
|
||||||
|
this.notificationsService.error('TODO implement editGroup (not yet implemented in backend) ');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start editing the selected group
|
||||||
|
* @param groupId ID of group to set as active
|
||||||
|
*/
|
||||||
|
setActiveGroup(groupId: string) {
|
||||||
|
this.groupDataService.cancelEditGroup();
|
||||||
|
this.groupDataService.findById(groupId)
|
||||||
|
.pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload())
|
||||||
|
.subscribe((group: Group) => {
|
||||||
|
this.groupDataService.editGroup(group);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start editing the selected group
|
||||||
|
* @param groupSelfLink SelfLink of group to set as active
|
||||||
|
*/
|
||||||
|
setActiveGroupWithLink(groupSelfLink: string) {
|
||||||
|
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => {
|
||||||
|
if (activeGroup === null) {
|
||||||
|
this.groupDataService.cancelEditGroup();
|
||||||
|
this.groupDataService.findByHref(groupSelfLink)
|
||||||
|
.pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload())
|
||||||
|
.subscribe((group: Group) => {
|
||||||
|
this.groupDataService.editGroup(group);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel the current edit when component is destroyed & unsub all subscriptions
|
||||||
|
*/
|
||||||
|
@HostListener('window:beforeunload')
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.onCancel();
|
||||||
|
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,124 @@
|
|||||||
|
<ng-container>
|
||||||
|
<h3 class="border-bottom pb-2">{{messagePrefix + '.head' | translate}}</h3>
|
||||||
|
|
||||||
|
<h4 id="search" class="border-bottom pb-2">{{messagePrefix + '.search.head' | translate}}
|
||||||
|
<button (click)="clearFormAndResetResult();"
|
||||||
|
class="btn btn-primary float-right">{{messagePrefix + '.button.see-all' | translate}}</button>
|
||||||
|
</h4>
|
||||||
|
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="row">
|
||||||
|
<div class="col-12 col-sm-3">
|
||||||
|
<select name="scope" id="scope" formControlName="scope" class="form-control" aria-label="Search scope">
|
||||||
|
<option value="metadata">{{messagePrefix + '.search.scope.metadata' | translate}}</option>
|
||||||
|
<option value="email">{{messagePrefix + '.search.scope.email' | translate}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-9 col-12">
|
||||||
|
<div class="form-group input-group">
|
||||||
|
<input type="text" name="query" id="query" formControlName="query"
|
||||||
|
class="form-control" aria-label="Search input">
|
||||||
|
<span class="input-group-append">
|
||||||
|
<button type="submit"
|
||||||
|
class="search-button btn btn-secondary">{{ messagePrefix + '.search.button' | translate }}</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<ds-pagination *ngIf="(ePeopleSearch | async)?.payload.totalElements > 0"
|
||||||
|
[paginationOptions]="configSearch"
|
||||||
|
[pageInfoState]="(ePeopleSearch | async)?.payload"
|
||||||
|
[collectionSize]="(ePeopleSearch | async)?.payload?.totalElements"
|
||||||
|
[hideGear]="true"
|
||||||
|
[hidePagerWhenSinglePage]="true"
|
||||||
|
(pageChange)="onPageChangeSearch($event)">
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="epersonsSearch" class="table table-striped table-hover table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">{{messagePrefix + '.table.id' | translate}}</th>
|
||||||
|
<th scope="col">{{messagePrefix + '.table.name' | translate}}</th>
|
||||||
|
<th>{{messagePrefix + '.table.edit' | translate}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let ePerson of (ePeopleSearch | async)?.payload?.page">
|
||||||
|
<td>{{ePerson.id}}</td>
|
||||||
|
<td><a (click)="ePersonDataService.startEditingNewEPerson(ePerson)"
|
||||||
|
[routerLink]="[ePersonDataService.getEPeoplePageRouterLink()]">{{ePerson.name}}</a></td>
|
||||||
|
<td>
|
||||||
|
<div class="btn-group edit-field">
|
||||||
|
<button *ngIf="(isMemberOfGroup(ePerson) | async)"
|
||||||
|
(click)="deleteMemberFromGroup(ePerson)"
|
||||||
|
class="btn btn-outline-danger btn-sm"
|
||||||
|
title="{{messagePrefix + '.table.edit.buttons.remove' | translate: {name: ePerson.name} }}">
|
||||||
|
<i class="fas fa-trash-alt fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button *ngIf="!(isMemberOfGroup(ePerson) | async)"
|
||||||
|
(click)="addMemberToGroup(ePerson)"
|
||||||
|
class="btn btn-outline-primary btn-sm"
|
||||||
|
title="{{messagePrefix + '.table.edit.buttons.add' | translate: {name: ePerson.name} }}">
|
||||||
|
<i class="fas fa-plus fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</ds-pagination>
|
||||||
|
|
||||||
|
<div *ngIf="(ePeopleSearch | async)?.payload.totalElements == 0 && searchDone"
|
||||||
|
class="alert alert-info w-100 mb-2"
|
||||||
|
role="alert">
|
||||||
|
{{messagePrefix + '.no-items' | translate}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>{{messagePrefix + '.headMembers' | translate}}</h4>
|
||||||
|
|
||||||
|
<ds-pagination *ngIf="(ePeopleMembersOfGroup | async)?.payload.totalElements > 0"
|
||||||
|
[paginationOptions]="config"
|
||||||
|
[pageInfoState]="(ePeopleMembersOfGroup | async)?.payload"
|
||||||
|
[collectionSize]="(ePeopleMembersOfGroup | async)?.payload?.totalElements"
|
||||||
|
[hideGear]="true"
|
||||||
|
[hidePagerWhenSinglePage]="true"
|
||||||
|
(pageChange)="onPageChange($event)">
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="ePeopleMembersOfGroup" class="table table-striped table-hover table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">{{messagePrefix + '.table.id' | translate}}</th>
|
||||||
|
<th scope="col">{{messagePrefix + '.table.name' | translate}}</th>
|
||||||
|
<th>{{messagePrefix + '.table.edit' | translate}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let ePerson of (ePeopleMembersOfGroup | async)?.payload?.page">
|
||||||
|
<td>{{ePerson.id}}</td>
|
||||||
|
<td><a (click)="ePersonDataService.startEditingNewEPerson(ePerson)"
|
||||||
|
[routerLink]="[ePersonDataService.getEPeoplePageRouterLink()]">{{ePerson.name}}</a></td>
|
||||||
|
<td>
|
||||||
|
<div class="btn-group edit-field">
|
||||||
|
<button (click)="deleteMemberFromGroup(ePerson)"
|
||||||
|
class="btn btn-outline-danger btn-sm"
|
||||||
|
title="{{messagePrefix + '.table.edit.buttons.remove' | translate: {name: ePerson.name} }}">
|
||||||
|
<i class="fas fa-trash-alt fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</ds-pagination>
|
||||||
|
|
||||||
|
<div *ngIf="(ePeopleMembersOfGroup | async)?.payload.totalElements == 0" class="alert alert-info w-100 mb-2"
|
||||||
|
role="alert">
|
||||||
|
{{messagePrefix + '.no-members-yet' | translate}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</ng-container>
|
@@ -0,0 +1,241 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, fakeAsync, flush, inject, TestBed, tick } from '@angular/core/testing';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { BrowserModule, By } from '@angular/platform-browser';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { RestResponse } from '../../../../../core/cache/response.models';
|
||||||
|
import { PaginatedList } from '../../../../../core/data/paginated-list';
|
||||||
|
import { RemoteData } from '../../../../../core/data/remote-data';
|
||||||
|
import { EPersonDataService } from '../../../../../core/eperson/eperson-data.service';
|
||||||
|
import { GroupDataService } from '../../../../../core/eperson/group-data.service';
|
||||||
|
import { EPerson } from '../../../../../core/eperson/models/eperson.model';
|
||||||
|
import { Group } from '../../../../../core/eperson/models/group.model';
|
||||||
|
import { PageInfo } from '../../../../../core/shared/page-info.model';
|
||||||
|
import { FormBuilderService } from '../../../../../shared/form/builder/form-builder.service';
|
||||||
|
import { getMockFormBuilderService } from '../../../../../shared/mocks/mock-form-builder-service';
|
||||||
|
import { MockRouter } from '../../../../../shared/mocks/mock-router';
|
||||||
|
import { getMockTranslateService } from '../../../../../shared/mocks/mock-translate.service';
|
||||||
|
import { NotificationsService } from '../../../../../shared/notifications/notifications.service';
|
||||||
|
import { EPersonMock, EPersonMock2 } from '../../../../../shared/testing/eperson-mock';
|
||||||
|
import { GroupMock, GroupMock2 } from '../../../../../shared/testing/group-mock';
|
||||||
|
import { MockTranslateLoader } from '../../../../../shared/testing/mock-translate-loader';
|
||||||
|
import { NotificationsServiceStub } from '../../../../../shared/testing/notifications-service-stub';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/testing/utils';
|
||||||
|
import { MembersListComponent } from './members-list.component';
|
||||||
|
|
||||||
|
describe('MembersListComponent', () => {
|
||||||
|
let component: MembersListComponent;
|
||||||
|
let fixture: ComponentFixture<MembersListComponent>;
|
||||||
|
let translateService: TranslateService;
|
||||||
|
let builderService: FormBuilderService;
|
||||||
|
let ePersonDataServiceStub: any;
|
||||||
|
let groupsDataServiceStub: any;
|
||||||
|
let activeGroup;
|
||||||
|
let allEPersons;
|
||||||
|
let allGroups;
|
||||||
|
let epersonMembers;
|
||||||
|
let subgroupMembers;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
activeGroup = GroupMock;
|
||||||
|
epersonMembers = [EPersonMock2];
|
||||||
|
subgroupMembers = [GroupMock2];
|
||||||
|
allEPersons = [EPersonMock, EPersonMock2];
|
||||||
|
allGroups = [GroupMock, GroupMock2];
|
||||||
|
ePersonDataServiceStub = {
|
||||||
|
activeGroup: activeGroup,
|
||||||
|
epersonMembers: epersonMembers,
|
||||||
|
subgroupMembers: subgroupMembers,
|
||||||
|
findAllByHref(href: string): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
|
return createSuccessfulRemoteDataObject$(new PaginatedList<EPerson>(new PageInfo(), groupsDataServiceStub.getEPersonMembers()))
|
||||||
|
},
|
||||||
|
searchByScope(scope: string, query: string): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
|
if (query === '') {
|
||||||
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), allEPersons))
|
||||||
|
}
|
||||||
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), []))
|
||||||
|
},
|
||||||
|
clearEPersonRequests() {
|
||||||
|
// empty
|
||||||
|
},
|
||||||
|
clearLinkRequests() {
|
||||||
|
// empty
|
||||||
|
},
|
||||||
|
getEPeoplePageRouterLink(): string {
|
||||||
|
return '/admin/access-control/epeople';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
groupsDataServiceStub = {
|
||||||
|
activeGroup: activeGroup,
|
||||||
|
epersonMembers: epersonMembers,
|
||||||
|
subgroupMembers: subgroupMembers,
|
||||||
|
allGroups: allGroups,
|
||||||
|
getActiveGroup(): Observable<Group> {
|
||||||
|
return observableOf(activeGroup);
|
||||||
|
},
|
||||||
|
getEPersonMembers() {
|
||||||
|
return this.epersonMembers;
|
||||||
|
},
|
||||||
|
searchGroups(query: string): Observable<RemoteData<PaginatedList<Group>>> {
|
||||||
|
if (query === '') {
|
||||||
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), this.allGroups))
|
||||||
|
}
|
||||||
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), []))
|
||||||
|
},
|
||||||
|
addMemberToGroup(parentGroup, eperson: EPerson): Observable<RestResponse> {
|
||||||
|
this.epersonMembers = [...this.epersonMembers, eperson];
|
||||||
|
return observableOf(new RestResponse(true, 200, 'Success'));
|
||||||
|
},
|
||||||
|
clearGroupsRequests() {
|
||||||
|
// empty
|
||||||
|
},
|
||||||
|
clearGroupLinkRequests() {
|
||||||
|
// empty
|
||||||
|
},
|
||||||
|
getGroupEditPageRouterLink(group: Group): string {
|
||||||
|
return '/admin/access-control/groups/' + group.id;
|
||||||
|
},
|
||||||
|
deleteMemberFromGroup(parentGroup, epersonToDelete: EPerson): Observable<RestResponse> {
|
||||||
|
this.epersonMembers = this.epersonMembers.find((eperson: EPerson) => {
|
||||||
|
if (eperson.id !== epersonToDelete.id) {
|
||||||
|
return eperson;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (this.epersonMembers === undefined) {
|
||||||
|
this.epersonMembers = []
|
||||||
|
}
|
||||||
|
return observableOf(new RestResponse(true, 200, 'Success'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
builderService = getMockFormBuilderService();
|
||||||
|
translateService = getMockTranslateService();
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
|
||||||
|
TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: MockTranslateLoader
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
declarations: [MembersListComponent],
|
||||||
|
providers: [MembersListComponent,
|
||||||
|
{ provide: EPersonDataService, useValue: ePersonDataServiceStub },
|
||||||
|
{ provide: GroupDataService, useValue: groupsDataServiceStub },
|
||||||
|
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
||||||
|
{ provide: FormBuilderService, useValue: builderService },
|
||||||
|
{ provide: Router, useValue: new MockRouter() },
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(MembersListComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
afterEach(fakeAsync(() => {
|
||||||
|
fixture.destroy();
|
||||||
|
flush();
|
||||||
|
component = null;
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should create MembersListComponent', inject([MembersListComponent], (comp: MembersListComponent) => {
|
||||||
|
expect(comp).toBeDefined();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show list of eperson members of current active group', () => {
|
||||||
|
const epersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child'));
|
||||||
|
expect(epersonIdsFound.length).toEqual(1);
|
||||||
|
epersonMembers.map((eperson: EPerson) => {
|
||||||
|
expect(epersonIdsFound.find((foundEl) => {
|
||||||
|
return (foundEl.nativeElement.textContent.trim() === eperson.uuid);
|
||||||
|
})).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('search', () => {
|
||||||
|
describe('when searching without query', () => {
|
||||||
|
let epersonsFound;
|
||||||
|
beforeEach(fakeAsync(() => {
|
||||||
|
component.search({ scope: 'metadata', query: '' });
|
||||||
|
tick();
|
||||||
|
fixture.detectChanges();
|
||||||
|
epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr'));
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should display all epersons', () => {
|
||||||
|
expect(epersonsFound.length).toEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('if eperson is already a eperson', () => {
|
||||||
|
it('should have delete button, else it should have add button', () => {
|
||||||
|
activeGroup.epersons.map((eperson: EPerson) => {
|
||||||
|
epersonsFound.map((foundEPersonRowElement) => {
|
||||||
|
if (foundEPersonRowElement.debugElement !== undefined) {
|
||||||
|
const epersonId = foundEPersonRowElement.debugElement.query(By.css('td:first-child'));
|
||||||
|
const addButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-plus'));
|
||||||
|
const deleteButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-trash-alt'));
|
||||||
|
if (epersonId.nativeElement.textContent === eperson.id) {
|
||||||
|
expect(addButton).toBeUndefined();
|
||||||
|
expect(deleteButton).toBeDefined();
|
||||||
|
} else {
|
||||||
|
expect(deleteButton).toBeUndefined();
|
||||||
|
expect(addButton).toBeDefined();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('if first add button is pressed', () => {
|
||||||
|
beforeEach(fakeAsync(() => {
|
||||||
|
const addButton = fixture.debugElement.query(By.css('#epersonsSearch tbody .fa-plus'));
|
||||||
|
addButton.nativeElement.click();
|
||||||
|
tick();
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
it('all groups in search member of selected group', () => {
|
||||||
|
epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr'));
|
||||||
|
expect(epersonsFound.length).toEqual(2);
|
||||||
|
epersonsFound.map((foundEPersonRowElement) => {
|
||||||
|
if (foundEPersonRowElement.debugElement !== undefined) {
|
||||||
|
const addButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-plus'));
|
||||||
|
const deleteButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-trash-alt'));
|
||||||
|
expect(addButton).toBeUndefined();
|
||||||
|
expect(deleteButton).toBeDefined();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('if first delete button is pressed', () => {
|
||||||
|
beforeEach(fakeAsync(() => {
|
||||||
|
const addButton = fixture.debugElement.query(By.css('#epersonsSearch tbody .fa-trash-alt'));
|
||||||
|
addButton.nativeElement.click();
|
||||||
|
tick();
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
it('first eperson in search delete button, because now member', () => {
|
||||||
|
epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr'));
|
||||||
|
epersonsFound.map((foundEPersonRowElement) => {
|
||||||
|
if (foundEPersonRowElement.debugElement !== undefined) {
|
||||||
|
const addButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-plus'));
|
||||||
|
const deleteButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-trash-alt'));
|
||||||
|
expect(deleteButton).toBeUndefined();
|
||||||
|
expect(addButton).toBeDefined();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,247 @@
|
|||||||
|
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { FormBuilder } from '@angular/forms';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { Observable, of as observableOf, Subscription } from 'rxjs';
|
||||||
|
import { map, mergeMap, take } from 'rxjs/operators';
|
||||||
|
import { RestResponse } from '../../../../../core/cache/response.models';
|
||||||
|
import { PaginatedList } from '../../../../../core/data/paginated-list';
|
||||||
|
import { RemoteData } from '../../../../../core/data/remote-data';
|
||||||
|
import { EPersonDataService } from '../../../../../core/eperson/eperson-data.service';
|
||||||
|
import { GroupDataService } from '../../../../../core/eperson/group-data.service';
|
||||||
|
import { EPerson } from '../../../../../core/eperson/models/eperson.model';
|
||||||
|
import { Group } from '../../../../../core/eperson/models/group.model';
|
||||||
|
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../../core/shared/operators';
|
||||||
|
import { hasValue } from '../../../../../shared/empty.util';
|
||||||
|
import { NotificationsService } from '../../../../../shared/notifications/notifications.service';
|
||||||
|
import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-members-list',
|
||||||
|
templateUrl: './members-list.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* The list of members in the edit group page
|
||||||
|
*/
|
||||||
|
export class MembersListComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
messagePrefix: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EPeople being displayed in search result, initially all members, after search result of search
|
||||||
|
*/
|
||||||
|
ePeopleSearch: Observable<RemoteData<PaginatedList<EPerson>>>;
|
||||||
|
/**
|
||||||
|
* List of EPeople members of currently active group being edited
|
||||||
|
*/
|
||||||
|
ePeopleMembersOfGroup: Observable<RemoteData<PaginatedList<EPerson>>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pagination config used to display the list of EPeople that are result of EPeople search
|
||||||
|
*/
|
||||||
|
configSearch: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
|
||||||
|
id: 'search-members-list-pagination',
|
||||||
|
pageSize: 5,
|
||||||
|
currentPage: 1
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* Pagination config used to display the list of EPerson Membes of active group being edited
|
||||||
|
*/
|
||||||
|
config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
|
||||||
|
id: 'members-list-pagination',
|
||||||
|
pageSize: 5,
|
||||||
|
currentPage: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of subscriptions
|
||||||
|
*/
|
||||||
|
subs: Subscription[] = [];
|
||||||
|
|
||||||
|
// The search form
|
||||||
|
searchForm;
|
||||||
|
|
||||||
|
// Current search in edit group - epeople search form
|
||||||
|
currentSearchQuery: string;
|
||||||
|
currentSearchScope: string;
|
||||||
|
|
||||||
|
// Whether or not user has done a EPeople search yet
|
||||||
|
searchDone: boolean;
|
||||||
|
|
||||||
|
// current active group being edited
|
||||||
|
groupBeingEdited: Group;
|
||||||
|
|
||||||
|
constructor(private groupDataService: GroupDataService,
|
||||||
|
public ePersonDataService: EPersonDataService,
|
||||||
|
private translateService: TranslateService,
|
||||||
|
private notificationsService: NotificationsService,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private router: Router) {
|
||||||
|
this.currentSearchQuery = '';
|
||||||
|
this.currentSearchScope = 'metadata';
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.searchForm = this.formBuilder.group(({
|
||||||
|
scope: 'metadata',
|
||||||
|
query: '',
|
||||||
|
}));
|
||||||
|
this.subs.push(this.groupDataService.getActiveGroup().subscribe((activeGroup: Group) => {
|
||||||
|
if (activeGroup != null) {
|
||||||
|
this.groupBeingEdited = activeGroup;
|
||||||
|
this.forceUpdateEPeople(activeGroup);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event triggered when the user changes page on search result
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
onPageChangeSearch(event) {
|
||||||
|
this.configSearch.currentPage = event;
|
||||||
|
this.search({ scope: this.currentSearchScope, query: this.currentSearchQuery });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event triggered when the user changes page on EPerson embers of active group
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
onPageChange(event) {
|
||||||
|
this.ePeopleMembersOfGroup = this.ePersonDataService.findAllByHref(this.groupBeingEdited._links.epersons.href, {
|
||||||
|
currentPage: event,
|
||||||
|
elementsPerPage: this.config.pageSize
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
deleteMemberFromGroup(ePerson: EPerson) {
|
||||||
|
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => {
|
||||||
|
if (activeGroup != null) {
|
||||||
|
const response = this.groupDataService.deleteMemberFromGroup(activeGroup, ePerson);
|
||||||
|
this.showNotifications('deleteMember', response, ePerson.name, activeGroup);
|
||||||
|
this.forceUpdateEPeople(activeGroup);
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveGroup'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
addMemberToGroup(ePerson: EPerson) {
|
||||||
|
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => {
|
||||||
|
if (activeGroup != null) {
|
||||||
|
const response = this.groupDataService.addMemberToGroup(activeGroup, ePerson);
|
||||||
|
this.showNotifications('addMember', response, ePerson.name, activeGroup);
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveGroup'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.forceUpdateEPeople(this.groupBeingEdited, ePerson);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not 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.findAllByHref(group._links.epersons.href, {
|
||||||
|
currentPage: 0,
|
||||||
|
elementsPerPage: Number.MAX_SAFE_INTEGER
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
map((listEPeopleInGroup: PaginatedList<EPerson>) => listEPeopleInGroup.page.filter((ePersonInList: EPerson) => ePersonInList.id === possibleMember.id)),
|
||||||
|
map((epeople: EPerson[]) => epeople.length > 0))
|
||||||
|
} else {
|
||||||
|
return observableOf(false);
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search in the EPeople by name, email or metadata
|
||||||
|
* @param data Contains scope and query param
|
||||||
|
*/
|
||||||
|
search(data: any) {
|
||||||
|
const query: string = data.query;
|
||||||
|
const scope: string = data.scope;
|
||||||
|
if (query != null && this.currentSearchQuery !== query && this.groupBeingEdited) {
|
||||||
|
this.router.navigateByUrl(this.groupDataService.getGroupEditPageRouterLink(this.groupBeingEdited));
|
||||||
|
this.currentSearchQuery = query;
|
||||||
|
this.configSearch.currentPage = 1;
|
||||||
|
}
|
||||||
|
if (scope != null && this.currentSearchScope !== scope && this.groupBeingEdited) {
|
||||||
|
this.router.navigateByUrl(this.groupDataService.getGroupEditPageRouterLink(this.groupBeingEdited));
|
||||||
|
this.currentSearchScope = scope;
|
||||||
|
this.configSearch.currentPage = 1;
|
||||||
|
}
|
||||||
|
this.searchDone = true;
|
||||||
|
this.ePeopleSearch = this.ePersonDataService.searchByScope(this.currentSearchScope, this.currentSearchQuery, {
|
||||||
|
currentPage: this.configSearch.currentPage,
|
||||||
|
elementsPerPage: this.configSearch.pageSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force-update the list of EPeople by first clearing the cache related to EPeople, then performing
|
||||||
|
* a new REST call
|
||||||
|
* @param activeGroup Group currently being edited
|
||||||
|
*/
|
||||||
|
public forceUpdateEPeople(activeGroup: Group, ePersonToUpdate?: EPerson) {
|
||||||
|
if (ePersonToUpdate != null) {
|
||||||
|
this.ePersonDataService.clearLinkRequests(ePersonToUpdate._links.groups.href);
|
||||||
|
}
|
||||||
|
this.ePersonDataService.clearLinkRequests(activeGroup._links.epersons.href);
|
||||||
|
this.router.navigateByUrl(this.groupDataService.getGroupEditPageRouterLink(activeGroup));
|
||||||
|
this.ePeopleMembersOfGroup = this.ePersonDataService.findAllByHref(activeGroup._links.epersons.href, {
|
||||||
|
currentPage: this.configSearch.currentPage,
|
||||||
|
elementsPerPage: this.configSearch.pageSize
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* unsub all subscriptions
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a notification based on the success/failure of the request
|
||||||
|
* @param messageSuffix Suffix for message
|
||||||
|
* @param response RestResponse observable containing success/failure request
|
||||||
|
* @param nameObject Object request was about
|
||||||
|
* @param activeGroup Group currently being edited
|
||||||
|
*/
|
||||||
|
showNotifications(messageSuffix: string, response: Observable<RestResponse>, nameObject: string, activeGroup: Group) {
|
||||||
|
response.pipe(take(1)).subscribe((restResponse: RestResponse) => {
|
||||||
|
if (restResponse.isSuccessful) {
|
||||||
|
this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.success.' + messageSuffix, { name: nameObject }));
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.' + messageSuffix, { name: nameObject }));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset all input-fields to be empty and search all search
|
||||||
|
*/
|
||||||
|
clearFormAndResetResult() {
|
||||||
|
this.searchForm.patchValue({
|
||||||
|
query: '',
|
||||||
|
});
|
||||||
|
this.search({ query: '' });
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,117 @@
|
|||||||
|
<ng-container>
|
||||||
|
<h3 class="border-bottom pb-2">{{messagePrefix + '.head' | translate}}</h3>
|
||||||
|
|
||||||
|
<h4 id="search" class="border-bottom pb-2">{{messagePrefix + '.search.head' | translate}}
|
||||||
|
<button (click)="clearFormAndResetResult();"
|
||||||
|
class="btn btn-primary float-right">{{messagePrefix + '.button.see-all' | translate}}</button>
|
||||||
|
</h4>
|
||||||
|
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="form-group input-group">
|
||||||
|
<input type="text" name="query" id="query" formControlName="query"
|
||||||
|
class="form-control" aria-label="Search input">
|
||||||
|
<span class="input-group-append">
|
||||||
|
<button type="submit"
|
||||||
|
class="search-button btn btn-secondary">{{ messagePrefix + '.search.button' | translate }}</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<ds-pagination *ngIf="(groupsSearch | async)?.payload.totalElements > 0"
|
||||||
|
[paginationOptions]="configSearch"
|
||||||
|
[pageInfoState]="(groupsSearch | async)?.payload"
|
||||||
|
[collectionSize]="(groupsSearch | async)?.payload?.totalElements"
|
||||||
|
[hideGear]="true"
|
||||||
|
[hidePagerWhenSinglePage]="true"
|
||||||
|
(pageChange)="onPageChangeSearch($event)">
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="groupsSearch" class="table table-striped table-hover table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">{{messagePrefix + '.table.id' | translate}}</th>
|
||||||
|
<th scope="col">{{messagePrefix + '.table.name' | translate}}</th>
|
||||||
|
<th>{{messagePrefix + '.table.edit' | translate}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let group of (groupsSearch | async)?.payload?.page">
|
||||||
|
<td>{{group.id}}</td>
|
||||||
|
<td><a (click)="groupDataService.startEditingNewGroup(group)"
|
||||||
|
[routerLink]="[groupDataService.getGroupEditPageRouterLink(group)]">{{group.name}}</a></td>
|
||||||
|
<td>
|
||||||
|
<div class="btn-group edit-field">
|
||||||
|
<button *ngIf="(isSubgroupOfGroup(group) | async) && !(isActiveGroup(group) | async)"
|
||||||
|
(click)="deleteSubgroupFromGroup(group)"
|
||||||
|
class="btn btn-outline-danger btn-sm deleteButton"
|
||||||
|
title="{{messagePrefix + '.table.edit.buttons.remove' | translate: {name: group.name} }}">
|
||||||
|
<i class="fas fa-trash-alt fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<p *ngIf="(isActiveGroup(group) | async)">{{ messagePrefix + '.table.edit.currentGroup' | translate }}</p>
|
||||||
|
|
||||||
|
<button *ngIf="!(isSubgroupOfGroup(group) | async) && !(isActiveGroup(group) | async)"
|
||||||
|
(click)="addSubgroupToGroup(group)"
|
||||||
|
class="btn btn-outline-primary btn-sm addButton"
|
||||||
|
title="{{messagePrefix + '.table.edit.buttons.add' | translate: {name: group.name} }}">
|
||||||
|
<i class="fas fa-plus fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</ds-pagination>
|
||||||
|
|
||||||
|
<div *ngIf="(groupsSearch | async)?.payload.totalElements == 0 && searchDone" class="alert alert-info w-100 mb-2"
|
||||||
|
role="alert">
|
||||||
|
{{messagePrefix + '.no-items' | translate}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>{{messagePrefix + '.headSubgroups' | translate}}</h4>
|
||||||
|
|
||||||
|
<ds-pagination *ngIf="(subgroupsOfGroup | async)?.payload.totalElements > 0"
|
||||||
|
[paginationOptions]="config"
|
||||||
|
[pageInfoState]="(subgroupsOfGroup | async)?.payload"
|
||||||
|
[collectionSize]="(subgroupsOfGroup | async)?.payload?.totalElements"
|
||||||
|
[hideGear]="true"
|
||||||
|
[hidePagerWhenSinglePage]="true"
|
||||||
|
(pageChange)="onPageChange($event)">
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="subgroupsOfGroup" class="table table-striped table-hover table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">{{messagePrefix + '.table.id' | translate}}</th>
|
||||||
|
<th scope="col">{{messagePrefix + '.table.name' | translate}}</th>
|
||||||
|
<th>{{messagePrefix + '.table.edit' | translate}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let group of (subgroupsOfGroup | async)?.payload?.page">
|
||||||
|
<td>{{group.id}}</td>
|
||||||
|
<td><a (click)="groupDataService.startEditingNewGroup(group)"
|
||||||
|
[routerLink]="[groupDataService.getGroupEditPageRouterLink(group)]">{{group.name}}</a></td>
|
||||||
|
<td>
|
||||||
|
<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: group.name} }}">
|
||||||
|
<i class="fas fa-trash-alt fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</ds-pagination>
|
||||||
|
|
||||||
|
<div *ngIf="(subgroupsOfGroup | async)?.payload.totalElements == 0" class="alert alert-info w-100 mb-2"
|
||||||
|
role="alert">
|
||||||
|
{{messagePrefix + '.no-subgroups-yet' | translate}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</ng-container>
|
@@ -0,0 +1,208 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, fakeAsync, flush, inject, TestBed, tick } from '@angular/core/testing';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { BrowserModule, By } from '@angular/platform-browser';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { RestResponse } from '../../../../../core/cache/response.models';
|
||||||
|
import { PaginatedList } from '../../../../../core/data/paginated-list';
|
||||||
|
import { RemoteData } from '../../../../../core/data/remote-data';
|
||||||
|
import { GroupDataService } from '../../../../../core/eperson/group-data.service';
|
||||||
|
import { Group } from '../../../../../core/eperson/models/group.model';
|
||||||
|
import { PageInfo } from '../../../../../core/shared/page-info.model';
|
||||||
|
import { FormBuilderService } from '../../../../../shared/form/builder/form-builder.service';
|
||||||
|
import { getMockFormBuilderService } from '../../../../../shared/mocks/mock-form-builder-service';
|
||||||
|
import { MockRouter } from '../../../../../shared/mocks/mock-router';
|
||||||
|
import { getMockTranslateService } from '../../../../../shared/mocks/mock-translate.service';
|
||||||
|
import { NotificationsService } from '../../../../../shared/notifications/notifications.service';
|
||||||
|
import { GroupMock, GroupMock2 } from '../../../../../shared/testing/group-mock';
|
||||||
|
import { MockTranslateLoader } from '../../../../../shared/testing/mock-translate-loader';
|
||||||
|
import { NotificationsServiceStub } from '../../../../../shared/testing/notifications-service-stub';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/testing/utils';
|
||||||
|
import { SubgroupsListComponent } from './subgroups-list.component';
|
||||||
|
|
||||||
|
describe('SubgroupsListComponent', () => {
|
||||||
|
let component: SubgroupsListComponent;
|
||||||
|
let fixture: ComponentFixture<SubgroupsListComponent>;
|
||||||
|
let translateService: TranslateService;
|
||||||
|
let builderService: FormBuilderService;
|
||||||
|
let ePersonDataServiceStub: any;
|
||||||
|
let groupsDataServiceStub: any;
|
||||||
|
let activeGroup;
|
||||||
|
let subgroups;
|
||||||
|
let allGroups;
|
||||||
|
let routerStub;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
activeGroup = GroupMock;
|
||||||
|
subgroups = [GroupMock2];
|
||||||
|
allGroups = [GroupMock, GroupMock2];
|
||||||
|
ePersonDataServiceStub = {};
|
||||||
|
groupsDataServiceStub = {
|
||||||
|
activeGroup: activeGroup,
|
||||||
|
subgroups: subgroups,
|
||||||
|
getActiveGroup(): Observable<Group> {
|
||||||
|
return observableOf(this.activeGroup);
|
||||||
|
},
|
||||||
|
getSubgroups(): Group {
|
||||||
|
return this.activeGroup;
|
||||||
|
},
|
||||||
|
findAllByHref(href: string): Observable<RemoteData<PaginatedList<Group>>> {
|
||||||
|
return createSuccessfulRemoteDataObject$(new PaginatedList<Group>(new PageInfo(), this.subgroups))
|
||||||
|
},
|
||||||
|
getGroupEditPageRouterLink(group: Group): string {
|
||||||
|
return '/admin/access-control/groups/' + group.id;
|
||||||
|
},
|
||||||
|
searchGroups(query: string): Observable<RemoteData<PaginatedList<Group>>> {
|
||||||
|
if (query === '') {
|
||||||
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), allGroups))
|
||||||
|
}
|
||||||
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), []))
|
||||||
|
},
|
||||||
|
addSubGroupToGroup(parentGroup, subgroup: Group): Observable<RestResponse> {
|
||||||
|
this.subgroups = [...this.subgroups, subgroup];
|
||||||
|
return observableOf(new RestResponse(true, 200, 'Success'));
|
||||||
|
},
|
||||||
|
clearGroupsRequests() {
|
||||||
|
// empty
|
||||||
|
},
|
||||||
|
clearGroupLinkRequests() {
|
||||||
|
// empty
|
||||||
|
},
|
||||||
|
deleteSubGroupFromGroup(parentGroup, subgroup: Group): Observable<RestResponse> {
|
||||||
|
this.subgroups = this.subgroups.find((group: Group) => {
|
||||||
|
if (group.id !== subgroup.id) {
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return observableOf(new RestResponse(true, 200, 'Success'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
routerStub = new MockRouter();
|
||||||
|
builderService = getMockFormBuilderService();
|
||||||
|
translateService = getMockTranslateService();
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
|
||||||
|
TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: MockTranslateLoader
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
declarations: [SubgroupsListComponent],
|
||||||
|
providers: [SubgroupsListComponent,
|
||||||
|
{ provide: GroupDataService, useValue: groupsDataServiceStub },
|
||||||
|
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
||||||
|
{ provide: FormBuilderService, useValue: builderService },
|
||||||
|
{ provide: Router, useValue: routerStub },
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SubgroupsListComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
afterEach(fakeAsync(() => {
|
||||||
|
fixture.destroy();
|
||||||
|
flush();
|
||||||
|
component = null;
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should create SubgroupsListComponent', inject([SubgroupsListComponent], (comp: SubgroupsListComponent) => {
|
||||||
|
expect(comp).toBeDefined();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show list of subgroups of current active group', () => {
|
||||||
|
const groupIdsFound = fixture.debugElement.queryAll(By.css('#subgroupsOfGroup tr td:first-child'));
|
||||||
|
expect(groupIdsFound.length).toEqual(1);
|
||||||
|
activeGroup.subgroups.map((group: Group) => {
|
||||||
|
expect(groupIdsFound.find((foundEl) => {
|
||||||
|
return (foundEl.nativeElement.textContent.trim() === group.uuid);
|
||||||
|
})).toBeTruthy();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('if first group delete button is pressed', () => {
|
||||||
|
let groupsFound;
|
||||||
|
beforeEach(fakeAsync(() => {
|
||||||
|
const addButton = fixture.debugElement.query(By.css('#subgroupsOfGroup tbody .deleteButton'));
|
||||||
|
addButton.triggerEventHandler('click', {
|
||||||
|
preventDefault: () => {/**/
|
||||||
|
}
|
||||||
|
});
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('search', () => {
|
||||||
|
describe('when searching with empty query', () => {
|
||||||
|
let groupsFound;
|
||||||
|
beforeEach(fakeAsync(() => {
|
||||||
|
component.search({ query: '' });
|
||||||
|
groupsFound = fixture.debugElement.queryAll(By.css('#groupsSearch tbody tr'));
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should display all groups', () => {
|
||||||
|
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 = fixture.debugElement.queryAll(By.css('#groupsSearch tbody tr td:first-child'));
|
||||||
|
allGroups.map((group: Group) => {
|
||||||
|
expect(groupIdsFound.find((foundEl) => {
|
||||||
|
return (foundEl.nativeElement.textContent.trim() === group.uuid);
|
||||||
|
})).toBeTruthy();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('if group is already a subgroup', () => {
|
||||||
|
it('should have delete button, else it should have add button', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
groupsFound = fixture.debugElement.queryAll(By.css('#groupsSearch tbody tr'));
|
||||||
|
const getSubgroups = groupsDataServiceStub.getSubgroups().subgroups;
|
||||||
|
if (getSubgroups !== undefined && getSubgroups.length > 0) {
|
||||||
|
groupsFound.map((foundGroupRowElement) => {
|
||||||
|
if (foundGroupRowElement.debugElement !== undefined) {
|
||||||
|
const addButton = foundGroupRowElement.debugElement.query(By.css('td:last-child .fa-plus'));
|
||||||
|
const deleteButton = foundGroupRowElement.debugElement.query(By.css('td:last-child .fa-trash-alt'));
|
||||||
|
expect(addButton).toBeUndefined();
|
||||||
|
expect(deleteButton).toBeDefined();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
getSubgroups.map((group: Group) => {
|
||||||
|
groupsFound.map((foundGroupRowElement) => {
|
||||||
|
if (foundGroupRowElement.debugElement !== undefined) {
|
||||||
|
const groupId = foundGroupRowElement.debugElement.query(By.css('td:first-child'));
|
||||||
|
const addButton = foundGroupRowElement.debugElement.query(By.css('td:last-child .fa-plus'));
|
||||||
|
const deleteButton = foundGroupRowElement.debugElement.query(By.css('td:last-child .fa-trash-alt'));
|
||||||
|
if (groupId.nativeElement.textContent === group.id) {
|
||||||
|
expect(addButton).toBeUndefined();
|
||||||
|
expect(deleteButton).toBeDefined();
|
||||||
|
} else {
|
||||||
|
expect(deleteButton).toBeUndefined();
|
||||||
|
expect(addButton).toBeDefined();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,253 @@
|
|||||||
|
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { FormBuilder } from '@angular/forms';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { Observable, of as observableOf, Subscription } from 'rxjs';
|
||||||
|
import { map, mergeMap, take } from 'rxjs/operators';
|
||||||
|
import { RestResponse } from '../../../../../core/cache/response.models';
|
||||||
|
import { PaginatedList } from '../../../../../core/data/paginated-list';
|
||||||
|
import { RemoteData } from '../../../../../core/data/remote-data';
|
||||||
|
import { GroupDataService } from '../../../../../core/eperson/group-data.service';
|
||||||
|
import { Group } from '../../../../../core/eperson/models/group.model';
|
||||||
|
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../../core/shared/operators';
|
||||||
|
import { hasValue } from '../../../../../shared/empty.util';
|
||||||
|
import { NotificationsService } from '../../../../../shared/notifications/notifications.service';
|
||||||
|
import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-subgroups-list',
|
||||||
|
templateUrl: './subgroups-list.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* The list of subgroups in the edit group page
|
||||||
|
*/
|
||||||
|
export class SubgroupsListComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
messagePrefix: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of search groups, initially all groups
|
||||||
|
*/
|
||||||
|
groupsSearch: Observable<RemoteData<PaginatedList<Group>>>;
|
||||||
|
/**
|
||||||
|
* List of all subgroups of group being edited
|
||||||
|
*/
|
||||||
|
subgroupsOfGroup: Observable<RemoteData<PaginatedList<Group>>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of subscriptions
|
||||||
|
*/
|
||||||
|
subs: Subscription[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pagination config used to display the list of groups that are result of groups search
|
||||||
|
*/
|
||||||
|
configSearch: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
|
||||||
|
id: 'search-subgroups-list-pagination',
|
||||||
|
pageSize: 5,
|
||||||
|
currentPage: 1
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* Pagination config used to display the list of subgroups of currently active group being edited
|
||||||
|
*/
|
||||||
|
config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
|
||||||
|
id: 'subgroups-list-pagination',
|
||||||
|
pageSize: 5,
|
||||||
|
currentPage: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
// The search form
|
||||||
|
searchForm;
|
||||||
|
|
||||||
|
// Current search in edit group - groups search form
|
||||||
|
currentSearchQuery: string;
|
||||||
|
|
||||||
|
// Whether or not user has done a Groups search yet
|
||||||
|
searchDone: boolean;
|
||||||
|
|
||||||
|
// current active group being edited
|
||||||
|
groupBeingEdited: Group;
|
||||||
|
|
||||||
|
constructor(public groupDataService: GroupDataService,
|
||||||
|
private translateService: TranslateService,
|
||||||
|
private notificationsService: NotificationsService,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private router: Router) {
|
||||||
|
this.currentSearchQuery = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.searchForm = this.formBuilder.group(({
|
||||||
|
query: '',
|
||||||
|
}));
|
||||||
|
this.subs.push(this.groupDataService.getActiveGroup().subscribe((activeGroup: Group) => {
|
||||||
|
if (activeGroup != null) {
|
||||||
|
this.groupBeingEdited = activeGroup;
|
||||||
|
this.forceUpdateGroups(activeGroup);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event triggered when the user changes page on search result
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
onPageChangeSearch(event) {
|
||||||
|
this.configSearch.currentPage = event;
|
||||||
|
this.search({ query: this.currentSearchQuery });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event triggered when the user changes page on subgroups of active group
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
onPageChange(event) {
|
||||||
|
this.subgroupsOfGroup = this.groupDataService.findAllByHref(this.groupBeingEdited._links.subgroups.href, {
|
||||||
|
currentPage: event,
|
||||||
|
elementsPerPage: this.config.pageSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.findAllByHref(activeGroup._links.subgroups.href, {
|
||||||
|
currentPage: 0,
|
||||||
|
elementsPerPage: Number.MAX_SAFE_INTEGER
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
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
|
||||||
|
* @param subgroup Group we want to delete from the subgroups of the group currently being edited
|
||||||
|
*/
|
||||||
|
deleteSubgroupFromGroup(subgroup: Group) {
|
||||||
|
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => {
|
||||||
|
if (activeGroup != null) {
|
||||||
|
const response = this.groupDataService.deleteSubGroupFromGroup(activeGroup, subgroup);
|
||||||
|
this.showNotifications('deleteSubgroup', response, subgroup.name, activeGroup);
|
||||||
|
this.forceUpdateGroups(activeGroup);
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveGroup'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds given subgroup to the group currently being edited
|
||||||
|
* @param subgroup Subgroup to add to group currently being edited
|
||||||
|
*/
|
||||||
|
addSubgroupToGroup(subgroup: Group) {
|
||||||
|
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => {
|
||||||
|
if (activeGroup != null) {
|
||||||
|
if (activeGroup.uuid !== subgroup.uuid) {
|
||||||
|
const response = this.groupDataService.addSubGroupToGroup(activeGroup, subgroup);
|
||||||
|
this.showNotifications('addSubgroup', response, subgroup.name, activeGroup);
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.subgroupToAddIsActiveGroup'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveGroup'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.forceUpdateGroups(this.groupBeingEdited);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search in the groups (searches by group name and by uuid exact match)
|
||||||
|
* @param data Contains query param
|
||||||
|
*/
|
||||||
|
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.groupsSearch = this.groupDataService.searchGroups(this.currentSearchQuery, {
|
||||||
|
currentPage: this.configSearch.currentPage,
|
||||||
|
elementsPerPage: this.configSearch.pageSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force-update the list of groups by first clearing the cache of results of this active groups' subgroups, then performing a new REST call
|
||||||
|
* @param activeGroup Group currently being edited
|
||||||
|
*/
|
||||||
|
public forceUpdateGroups(activeGroup: Group) {
|
||||||
|
this.groupDataService.clearGroupLinkRequests(activeGroup._links.subgroups.href);
|
||||||
|
this.router.navigateByUrl(this.groupDataService.getGroupEditPageRouterLink(activeGroup));
|
||||||
|
this.subgroupsOfGroup = this.groupDataService.findAllByHref(activeGroup._links.subgroups.href, {
|
||||||
|
currentPage: this.config.currentPage,
|
||||||
|
elementsPerPage: this.config.pageSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* unsub all subscriptions
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a notification based on the success/failure of the request
|
||||||
|
* @param messageSuffix Suffix for message
|
||||||
|
* @param response RestResponse observable containing success/failure request
|
||||||
|
* @param nameObject Object request was about
|
||||||
|
* @param activeGroup Group currently being edited
|
||||||
|
*/
|
||||||
|
showNotifications(messageSuffix: string, response: Observable<RestResponse>, nameObject: string, activeGroup: Group) {
|
||||||
|
response.pipe(take(1)).subscribe((restResponse: RestResponse) => {
|
||||||
|
if (restResponse.isSuccessful) {
|
||||||
|
this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.success.' + messageSuffix, { name: nameObject }));
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.' + messageSuffix, { name: nameObject }));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset all input-fields to be empty and search all search
|
||||||
|
*/
|
||||||
|
clearFormAndResetResult() {
|
||||||
|
this.searchForm.patchValue({
|
||||||
|
query: '',
|
||||||
|
});
|
||||||
|
this.search({ query: '' });
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,49 @@
|
|||||||
|
import { Action } from '@ngrx/store';
|
||||||
|
import { Group } from '../../../core/eperson/models/group.model';
|
||||||
|
import { type } from '../../../shared/ngrx/type';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For each action type in an action group, make a simple
|
||||||
|
* enum object for all of this group's action types.
|
||||||
|
*
|
||||||
|
* The 'type' utility function coerces strings into string
|
||||||
|
* literal types and runs a simple check to guarantee all
|
||||||
|
* action types in the application are unique.
|
||||||
|
*/
|
||||||
|
export const GroupRegistryActionTypes = {
|
||||||
|
|
||||||
|
EDIT_GROUP: type('dspace/epeople-registry/EDIT_GROUP'),
|
||||||
|
CANCEL_EDIT_GROUP: type('dspace/epeople-registry/CANCEL_EDIT_GROUP'),
|
||||||
|
};
|
||||||
|
|
||||||
|
/* tslint:disable:max-classes-per-file */
|
||||||
|
/**
|
||||||
|
* Used to edit a Group in the Group registry
|
||||||
|
*/
|
||||||
|
export class GroupRegistryEditGroupAction implements Action {
|
||||||
|
type = GroupRegistryActionTypes.EDIT_GROUP;
|
||||||
|
|
||||||
|
group: Group;
|
||||||
|
|
||||||
|
constructor(group: Group) {
|
||||||
|
this.group = group;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to cancel the editing of a Group in the Group registry
|
||||||
|
*/
|
||||||
|
export class GroupRegistryCancelGroupAction implements Action {
|
||||||
|
type = GroupRegistryActionTypes.CANCEL_EDIT_GROUP;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tslint:enable:max-classes-per-file */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export a type alias of all actions in this action group
|
||||||
|
* so that reducers can easily compose action types
|
||||||
|
* These are all the actions to perform on the EPeople registry state
|
||||||
|
*/
|
||||||
|
export type GroupRegistryAction
|
||||||
|
= GroupRegistryEditGroupAction
|
||||||
|
| GroupRegistryCancelGroupAction
|
@@ -0,0 +1,54 @@
|
|||||||
|
import { GroupMock } from '../../../shared/testing/group-mock';
|
||||||
|
import { GroupRegistryCancelGroupAction, GroupRegistryEditGroupAction } from './group-registry.actions';
|
||||||
|
import { groupRegistryReducer, GroupRegistryState } from './group-registry.reducers';
|
||||||
|
|
||||||
|
const initialState: GroupRegistryState = {
|
||||||
|
editGroup: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const editState: GroupRegistryState = {
|
||||||
|
editGroup: GroupMock,
|
||||||
|
};
|
||||||
|
|
||||||
|
class NullAction extends GroupRegistryEditGroupAction {
|
||||||
|
type = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('groupRegistryReducer', () => {
|
||||||
|
|
||||||
|
it('should return the current state when no valid actions have been made', () => {
|
||||||
|
const state = initialState;
|
||||||
|
const action = new NullAction();
|
||||||
|
const newState = groupRegistryReducer(state, action);
|
||||||
|
|
||||||
|
expect(newState).toEqual(state);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should start with an initial state', () => {
|
||||||
|
const state = initialState;
|
||||||
|
const action = new NullAction();
|
||||||
|
const initState = groupRegistryReducer(undefined, action);
|
||||||
|
|
||||||
|
expect(initState).toEqual(state);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update the current state to change the editGroup to a new group when GroupRegistryEditGroupAction is dispatched', () => {
|
||||||
|
const state = editState;
|
||||||
|
const action = new GroupRegistryEditGroupAction(GroupMock);
|
||||||
|
const newState = groupRegistryReducer(state, action);
|
||||||
|
|
||||||
|
expect(newState.editGroup).toEqual(GroupMock);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update the current state to remove the editGroup from the state when GroupRegistryCancelGroupAction is dispatched', () => {
|
||||||
|
const state = editState;
|
||||||
|
const action = new GroupRegistryCancelGroupAction();
|
||||||
|
const newState = groupRegistryReducer(state, action);
|
||||||
|
|
||||||
|
expect(newState.editGroup).toEqual(null);
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,43 @@
|
|||||||
|
import { Group } from '../../../core/eperson/models/group.model';
|
||||||
|
import { GroupRegistryAction, GroupRegistryActionTypes, GroupRegistryEditGroupAction } from './group-registry.actions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The metadata registry state.
|
||||||
|
* @interface GroupRegistryState
|
||||||
|
*/
|
||||||
|
export interface GroupRegistryState {
|
||||||
|
editGroup: Group;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initial state.
|
||||||
|
*/
|
||||||
|
const initialState: GroupRegistryState = {
|
||||||
|
editGroup: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reducer that handles GroupRegistryActions to modify Groups
|
||||||
|
* @param state The current GroupRegistryState
|
||||||
|
* @param action The GroupRegistryAction to perform on the state
|
||||||
|
*/
|
||||||
|
export function groupRegistryReducer(state = initialState, action: GroupRegistryAction): GroupRegistryState {
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
|
||||||
|
case GroupRegistryActionTypes.EDIT_GROUP: {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
editGroup: (action as GroupRegistryEditGroupAction).group
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
case GroupRegistryActionTypes.CANCEL_EDIT_GROUP: {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
editGroup: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,84 @@
|
|||||||
|
<div class="container">
|
||||||
|
<div class="groups-registry row">
|
||||||
|
<div class="col-12">
|
||||||
|
|
||||||
|
<h2 id="header" class="border-bottom pb-2">{{messagePrefix + 'head' | translate}}</h2>
|
||||||
|
|
||||||
|
<div class="button-row top d-flex pb-2">
|
||||||
|
<button class="mr-auto btn btn-success"
|
||||||
|
[routerLink]="['newGroup']">
|
||||||
|
<i class="fas fa-plus"></i>
|
||||||
|
<span class="d-none d-sm-inline"> {{messagePrefix + 'button.add' | translate}}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 id="search" class="border-bottom pb-2">{{messagePrefix + 'search.head' | translate}}
|
||||||
|
<button (click)="clearFormAndResetResult();"
|
||||||
|
class="btn btn-primary float-right">{{messagePrefix + 'button.see-all' | translate}}</button>
|
||||||
|
</h3>
|
||||||
|
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="form-group input-group">
|
||||||
|
<input type="text" name="query" id="query" formControlName="query"
|
||||||
|
class="form-control" aria-label="Search input">
|
||||||
|
<span class="input-group-append">
|
||||||
|
<button type="submit"
|
||||||
|
class="search-button btn btn-secondary">{{ messagePrefix + 'search.button' | translate }}</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<ds-pagination
|
||||||
|
*ngIf="(groups | async)?.payload?.totalElements > 0"
|
||||||
|
[paginationOptions]="config"
|
||||||
|
[pageInfoState]="(groups | async)?.payload"
|
||||||
|
[collectionSize]="(groups | async)?.payload?.totalElements"
|
||||||
|
[hideGear]="true"
|
||||||
|
[hidePagerWhenSinglePage]="true"
|
||||||
|
(pageChange)="onPageChange($event)">
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="groups" class="table table-striped table-hover table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">{{messagePrefix + 'table.id' | translate}}</th>
|
||||||
|
<th scope="col">{{messagePrefix + 'table.name' | translate}}</th>
|
||||||
|
<th scope="col">{{messagePrefix + 'table.members' | translate}}</th>
|
||||||
|
<!-- <th scope="col">{{messagePrefix + 'table.comcol' | translate}}</th>-->
|
||||||
|
<th>{{messagePrefix + 'table.edit' | translate}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let group of (groups | async)?.payload?.page">
|
||||||
|
<td>{{group.id}}</td>
|
||||||
|
<td>{{group.name}}</td>
|
||||||
|
<td>{{(getMembers(group) | async)?.payload?.totalElements + (getSubgroups(group) | async)?.payload?.totalElements}}</td>
|
||||||
|
<!-- <td>{{getOptionalComColFromName(group.name)}}</td>-->
|
||||||
|
<td>
|
||||||
|
<div class="btn-group edit-field">
|
||||||
|
<button [routerLink]="groupService.getGroupEditPageRouterLink(group)"
|
||||||
|
class="btn btn-outline-primary btn-sm"
|
||||||
|
title="{{messagePrefix + 'table.edit.buttons.edit' | translate: {name: group.name} }}">
|
||||||
|
<i class="fas fa-edit fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="!group?.permanent" (click)="deleteGroup(group)"
|
||||||
|
class="btn btn-outline-danger btn-sm"
|
||||||
|
title="{{messagePrefix + 'table.edit.buttons.remove' | translate: {name: group.name} }}">
|
||||||
|
<i class="fas fa-trash-alt fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</ds-pagination>
|
||||||
|
|
||||||
|
<div *ngIf="(groups | async)?.payload?.totalElements == 0" class="alert alert-info w-100 mb-2" role="alert">
|
||||||
|
{{messagePrefix + 'no-items' | translate}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,139 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, fakeAsync, inject, TestBed, tick } from '@angular/core/testing';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { BrowserModule, By } from '@angular/platform-browser';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
|
||||||
|
import { GroupDataService } from '../../../core/eperson/group-data.service';
|
||||||
|
import { EPerson } from '../../../core/eperson/models/eperson.model';
|
||||||
|
import { Group } from '../../../core/eperson/models/group.model';
|
||||||
|
import { RouteService } from '../../../core/services/route.service';
|
||||||
|
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||||
|
import { MockRouter } from '../../../shared/mocks/mock-router';
|
||||||
|
import { MockTranslateLoader } from '../../../shared/mocks/mock-translate-loader';
|
||||||
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
|
import { EPersonMock, EPersonMock2 } from '../../../shared/testing/eperson-mock';
|
||||||
|
import { GroupMock, GroupMock2 } from '../../../shared/testing/group-mock';
|
||||||
|
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
|
||||||
|
import { routeServiceStub } from '../../../shared/testing/route-service-stub';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
|
||||||
|
import { GroupsRegistryComponent } from './groups-registry.component';
|
||||||
|
|
||||||
|
describe('GroupRegistryComponent', () => {
|
||||||
|
let component: GroupsRegistryComponent;
|
||||||
|
let fixture: ComponentFixture<GroupsRegistryComponent>;
|
||||||
|
let ePersonDataServiceStub: any;
|
||||||
|
let groupsDataServiceStub: any;
|
||||||
|
|
||||||
|
let mockGroups;
|
||||||
|
let mockEPeople;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
mockGroups = [GroupMock, GroupMock2];
|
||||||
|
mockEPeople = [EPersonMock, EPersonMock2];
|
||||||
|
ePersonDataServiceStub = {
|
||||||
|
findAllByHref(href: string): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
|
switch (href) {
|
||||||
|
case 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/epersons':
|
||||||
|
return createSuccessfulRemoteDataObject$(new PaginatedList(null, []));
|
||||||
|
case 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/epersons':
|
||||||
|
return createSuccessfulRemoteDataObject$(new PaginatedList(null, [EPersonMock]));
|
||||||
|
default:
|
||||||
|
return createSuccessfulRemoteDataObject$(new PaginatedList(null, []));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
groupsDataServiceStub = {
|
||||||
|
allGroups: mockGroups,
|
||||||
|
findAllByHref(href: string): Observable<RemoteData<PaginatedList<Group>>> {
|
||||||
|
switch (href) {
|
||||||
|
case 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/groups':
|
||||||
|
return createSuccessfulRemoteDataObject$(new PaginatedList(null, []));
|
||||||
|
case 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/groups':
|
||||||
|
return createSuccessfulRemoteDataObject$(new PaginatedList(null, [GroupMock2]));
|
||||||
|
default:
|
||||||
|
return createSuccessfulRemoteDataObject$(new PaginatedList(null, []));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getGroupEditPageRouterLink(group: Group): string {
|
||||||
|
return '/admin/access-control/groups/' + group.id;
|
||||||
|
},
|
||||||
|
getGroupRegistryRouterLink(): string {
|
||||||
|
return '/admin/access-control/groups';
|
||||||
|
},
|
||||||
|
searchGroups(query: string): Observable<RemoteData<PaginatedList<Group>>> {
|
||||||
|
if (query === '') {
|
||||||
|
return createSuccessfulRemoteDataObject$(new PaginatedList(null, this.allGroups));
|
||||||
|
}
|
||||||
|
const result = this.allGroups.find((group: Group) => {
|
||||||
|
return (group.id.includes(query))
|
||||||
|
});
|
||||||
|
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [result]));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
|
||||||
|
TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: MockTranslateLoader
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
declarations: [GroupsRegistryComponent],
|
||||||
|
providers: [GroupsRegistryComponent,
|
||||||
|
{ provide: EPersonDataService, useValue: ePersonDataServiceStub },
|
||||||
|
{ provide: GroupDataService, useValue: groupsDataServiceStub },
|
||||||
|
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
||||||
|
{ provide: RouteService, useValue: routeServiceStub },
|
||||||
|
{ provide: Router, useValue: new MockRouter() },
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(GroupsRegistryComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create GroupRegistryComponent', inject([GroupsRegistryComponent], (comp: GroupsRegistryComponent) => {
|
||||||
|
expect(comp).toBeDefined();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should display list of groups', () => {
|
||||||
|
const groupIdsFound = fixture.debugElement.queryAll(By.css('#groups tr td:first-child'));
|
||||||
|
expect(groupIdsFound.length).toEqual(2);
|
||||||
|
mockGroups.map((group: Group) => {
|
||||||
|
expect(groupIdsFound.find((foundEl) => {
|
||||||
|
return (foundEl.nativeElement.textContent.trim() === group.uuid);
|
||||||
|
})).toBeTruthy();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('search', () => {
|
||||||
|
describe('when searching with query', () => {
|
||||||
|
let groupIdsFound;
|
||||||
|
beforeEach(fakeAsync(() => {
|
||||||
|
component.search({ query: GroupMock2.id });
|
||||||
|
tick();
|
||||||
|
fixture.detectChanges();
|
||||||
|
groupIdsFound = fixture.debugElement.queryAll(By.css('#groups tr td:first-child'));
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should display search result', () => {
|
||||||
|
expect(groupIdsFound.length).toEqual(1);
|
||||||
|
expect(groupIdsFound.find((foundEl) => {
|
||||||
|
return (foundEl.nativeElement.textContent.trim() === GroupMock2.uuid);
|
||||||
|
})).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,154 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { FormBuilder } from '@angular/forms';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { take } from 'rxjs/operators';
|
||||||
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
|
||||||
|
import { GroupDataService } from '../../../core/eperson/group-data.service';
|
||||||
|
import { EPerson } from '../../../core/eperson/models/eperson.model';
|
||||||
|
import { Group } from '../../../core/eperson/models/group.model';
|
||||||
|
import { RouteService } from '../../../core/services/route.service';
|
||||||
|
import { hasValue } from '../../../shared/empty.util';
|
||||||
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
|
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-groups-registry',
|
||||||
|
templateUrl: './groups-registry.component.html',
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* A component used for managing all existing groups within the repository.
|
||||||
|
* The admin can create, edit or delete groups here.
|
||||||
|
*/
|
||||||
|
export class GroupsRegistryComponent implements OnInit {
|
||||||
|
|
||||||
|
messagePrefix = 'admin.access-control.groups.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pagination config used to display the list of groups
|
||||||
|
*/
|
||||||
|
config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
|
||||||
|
id: 'groups-list-pagination',
|
||||||
|
pageSize: 5,
|
||||||
|
currentPage: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of all the current groups within the repository or the result of the search
|
||||||
|
*/
|
||||||
|
groups: Observable<RemoteData<PaginatedList<Group>>>;
|
||||||
|
|
||||||
|
// The search form
|
||||||
|
searchForm;
|
||||||
|
|
||||||
|
// Current search in groups registry
|
||||||
|
currentSearchQuery: string;
|
||||||
|
|
||||||
|
constructor(private groupService: GroupDataService,
|
||||||
|
private ePersonDataService: EPersonDataService,
|
||||||
|
private translateService: TranslateService,
|
||||||
|
private notificationsService: NotificationsService,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
protected routeService: RouteService,
|
||||||
|
private router: Router) {
|
||||||
|
this.currentSearchQuery = '';
|
||||||
|
this.searchForm = this.formBuilder.group(({
|
||||||
|
query: this.currentSearchQuery,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.search({ query: this.currentSearchQuery });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event triggered when the user changes page
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
onPageChange(event) {
|
||||||
|
this.config.currentPage = event;
|
||||||
|
this.search({ query: this.currentSearchQuery })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search in the groups (searches by group name and by uuid exact match)
|
||||||
|
* @param data Contains query param
|
||||||
|
*/
|
||||||
|
search(data: any) {
|
||||||
|
const query: string = data.query;
|
||||||
|
if (query != null && this.currentSearchQuery !== query) {
|
||||||
|
this.router.navigateByUrl(this.groupService.getGroupRegistryRouterLink());
|
||||||
|
this.currentSearchQuery = query;
|
||||||
|
this.config.currentPage = 1;
|
||||||
|
}
|
||||||
|
this.groups = this.groupService.searchGroups(this.currentSearchQuery.trim(), {
|
||||||
|
currentPage: this.config.currentPage,
|
||||||
|
elementsPerPage: this.config.pageSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete Group
|
||||||
|
*/
|
||||||
|
deleteGroup(group: Group) {
|
||||||
|
// TODO (backend)
|
||||||
|
console.log('TODO implement editGroup', group);
|
||||||
|
this.notificationsService.error('TODO implement deleteGroup (not yet implemented in backend)');
|
||||||
|
if (hasValue(group.id)) {
|
||||||
|
this.groupService.deleteGroup(group).pipe(take(1)).subscribe((success: boolean) => {
|
||||||
|
if (success) {
|
||||||
|
this.notificationsService.success(this.translateService.get(this.messagePrefix + 'notification.deleted.success', { name: group.name }));
|
||||||
|
this.forceUpdateGroup();
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(this.translateService.get(this.messagePrefix + 'notification.deleted.failure', { name: group.name }));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force-update the list of groups by first clearing the cache related to groups, then performing a new REST call
|
||||||
|
*/
|
||||||
|
public forceUpdateGroup() {
|
||||||
|
this.groupService.clearGroupsRequests();
|
||||||
|
this.search({ query: this.currentSearchQuery })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the members (epersons embedded value of a group)
|
||||||
|
* @param group
|
||||||
|
*/
|
||||||
|
getMembers(group: Group): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
|
return this.ePersonDataService.findAllByHref(group._links.epersons.href);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the subgroups (groups embedded value of a group)
|
||||||
|
* @param group
|
||||||
|
*/
|
||||||
|
getSubgroups(group: Group): Observable<RemoteData<PaginatedList<Group>>> {
|
||||||
|
return this.groupService.findAllByHref(group._links.subgroups.href);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset all input-fields to be empty and search all search
|
||||||
|
*/
|
||||||
|
clearFormAndResetResult() {
|
||||||
|
this.searchForm.patchValue({
|
||||||
|
query: '',
|
||||||
|
});
|
||||||
|
this.search({ query: '' });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract optional UUID from a group name => To be resolved to community or collection with link
|
||||||
|
* (Or will be resolved in backend and added to group object, tbd) //TODO
|
||||||
|
* @param groupName
|
||||||
|
*/
|
||||||
|
getOptionalComColFromName(groupName: string): string {
|
||||||
|
return this.groupService.getUUIDFromString(groupName);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,9 +1,9 @@
|
|||||||
import { RouterModule } from '@angular/router';
|
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
import { RouterModule } from '@angular/router';
|
||||||
import { getAdminModulePath } from '../app-routing.module';
|
import { getAdminModulePath } from '../app-routing.module';
|
||||||
import { AdminSearchPageComponent } from './admin-search-page/admin-search-page.component';
|
import { AdminSearchPageComponent } from './admin-search-page/admin-search-page.component';
|
||||||
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||||
|
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
||||||
|
|
||||||
const REGISTRIES_MODULE_PATH = 'registries';
|
const REGISTRIES_MODULE_PATH = 'registries';
|
||||||
const ACCESS_CONTROL_MODULE_PATH = 'access-control';
|
const ACCESS_CONTROL_MODULE_PATH = 'access-control';
|
||||||
@@ -28,8 +28,8 @@ export function getRegistriesModulePath() {
|
|||||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||||
component: AdminSearchPageComponent,
|
component: AdminSearchPageComponent,
|
||||||
data: { title: 'admin.search.title', breadcrumbKey: 'admin.search' }
|
data: { title: 'admin.search.title', breadcrumbKey: 'admin.search' }
|
||||||
},
|
}
|
||||||
])
|
]),
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class AdminRoutingModule {
|
export class AdminRoutingModule {
|
||||||
|
@@ -336,7 +336,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.LINK,
|
||||||
text: 'menu.section.access_control_groups',
|
text: 'menu.section.access_control_groups',
|
||||||
link: ''
|
link: '/admin/access-control/groups'
|
||||||
} as LinkMenuItemModel,
|
} as LinkMenuItemModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -4,6 +4,10 @@ import {
|
|||||||
ePeopleRegistryReducer,
|
ePeopleRegistryReducer,
|
||||||
EPeopleRegistryState
|
EPeopleRegistryState
|
||||||
} from './+admin/admin-access-control/epeople-registry/epeople-registry.reducers';
|
} from './+admin/admin-access-control/epeople-registry/epeople-registry.reducers';
|
||||||
|
import {
|
||||||
|
groupRegistryReducer,
|
||||||
|
GroupRegistryState
|
||||||
|
} from './+admin/admin-access-control/group-registry/group-registry.reducers';
|
||||||
import {
|
import {
|
||||||
metadataRegistryReducer,
|
metadataRegistryReducer,
|
||||||
MetadataRegistryState
|
MetadataRegistryState
|
||||||
@@ -47,6 +51,7 @@ export interface AppState {
|
|||||||
relationshipLists: NameVariantListsState;
|
relationshipLists: NameVariantListsState;
|
||||||
communityList: CommunityListState;
|
communityList: CommunityListState;
|
||||||
epeopleRegistry: EPeopleRegistryState;
|
epeopleRegistry: EPeopleRegistryState;
|
||||||
|
groupRegistry: GroupRegistryState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const appReducers: ActionReducerMap<AppState> = {
|
export const appReducers: ActionReducerMap<AppState> = {
|
||||||
@@ -66,6 +71,7 @@ export const appReducers: ActionReducerMap<AppState> = {
|
|||||||
relationshipLists: nameVariantReducer,
|
relationshipLists: nameVariantReducer,
|
||||||
communityList: CommunityListReducer,
|
communityList: CommunityListReducer,
|
||||||
epeopleRegistry: ePeopleRegistryReducer,
|
epeopleRegistry: ePeopleRegistryReducer,
|
||||||
|
groupRegistry: groupRegistryReducer,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const routerStateSelector = (state: AppState) => state.router;
|
export const routerStateSelector = (state: AppState) => state.router;
|
||||||
|
@@ -2,7 +2,6 @@ import { HttpClient } from '@angular/common/http';
|
|||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { compare, Operation } from 'fast-json-patch';
|
import { compare, Operation } from 'fast-json-patch';
|
||||||
import { Observable, of as observableOf } from 'rxjs';
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
import * as uuidv4 from 'uuid/v4';
|
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
|
||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
|
@@ -19,7 +19,6 @@ import { EPersonMock, EPersonMock2 } from '../../shared/testing/eperson-mock';
|
|||||||
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub';
|
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
|
||||||
import { SearchParam } from '../cache/models/search-param.model';
|
import { SearchParam } from '../cache/models/search-param.model';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
|
||||||
import { CoreState } from '../core.reducers';
|
import { CoreState } from '../core.reducers';
|
||||||
import { ChangeAnalyzer } from '../data/change-analyzer';
|
import { ChangeAnalyzer } from '../data/change-analyzer';
|
||||||
import { PaginatedList } from '../data/paginated-list';
|
import { PaginatedList } from '../data/paginated-list';
|
||||||
@@ -39,20 +38,42 @@ describe('EPersonDataService', () => {
|
|||||||
let requestService: RequestService;
|
let requestService: RequestService;
|
||||||
let scheduler: TestScheduler;
|
let scheduler: TestScheduler;
|
||||||
|
|
||||||
const epeople = [EPersonMock, EPersonMock2];
|
let epeople;
|
||||||
|
|
||||||
const restEndpointURL = 'https://dspace.4science.it/dspace-spring-rest/api/eperson';
|
let restEndpointURL;
|
||||||
const epersonsEndpoint = `${restEndpointURL}/epersons`;
|
let epersonsEndpoint;
|
||||||
let halService: any = new HALEndpointServiceStub(restEndpointURL);
|
let halService: any;
|
||||||
const epeople$ = createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [epeople]));
|
let epeople$;
|
||||||
const rdbService = getMockRemoteDataBuildServiceHrefMap(undefined, { 'https://dspace.4science.it/dspace-spring-rest/api/eperson/epersons': epeople$ });
|
let rdbService;
|
||||||
const objectCache = Object.assign({
|
|
||||||
/* tslint:disable:no-empty */
|
let getRequestEntry$;
|
||||||
remove: () => {
|
|
||||||
},
|
function initTestService() {
|
||||||
hasBySelfLinkObservable: () => observableOf(false)
|
return new EPersonDataService(
|
||||||
/* tslint:enable:no-empty */
|
requestService,
|
||||||
}) as ObjectCacheService;
|
rdbService,
|
||||||
|
store,
|
||||||
|
null,
|
||||||
|
halService,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
new DummyChangeAnalyzer() as any
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
getRequestEntry$ = (successful: boolean) => {
|
||||||
|
return observableOf({
|
||||||
|
completed: true,
|
||||||
|
response: { isSuccessful: successful, payload: epeople } as any
|
||||||
|
} as RequestEntry)
|
||||||
|
};
|
||||||
|
restEndpointURL = 'https://dspace.4science.it/dspace-spring-rest/api/eperson';
|
||||||
|
epersonsEndpoint = `${restEndpointURL}/epersons`;
|
||||||
|
epeople = [EPersonMock, EPersonMock2];
|
||||||
|
epeople$ = createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [epeople]));
|
||||||
|
rdbService = getMockRemoteDataBuildServiceHrefMap(undefined, { 'https://dspace.4science.it/dspace-spring-rest/api/eperson/epersons': epeople$ });
|
||||||
|
halService = new HALEndpointServiceStub(restEndpointURL);
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -69,28 +90,10 @@ describe('EPersonDataService', () => {
|
|||||||
providers: [],
|
providers: [],
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
});
|
});
|
||||||
|
|
||||||
const getRequestEntry$ = (successful: boolean) => {
|
|
||||||
return observableOf({
|
|
||||||
completed: true,
|
|
||||||
response: { isSuccessful: successful, payload: epeople } as any
|
|
||||||
} as RequestEntry)
|
|
||||||
};
|
|
||||||
|
|
||||||
function initTestService() {
|
|
||||||
return new EPersonDataService(
|
|
||||||
requestService,
|
|
||||||
rdbService,
|
|
||||||
store,
|
|
||||||
null,
|
|
||||||
halService,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
new DummyChangeAnalyzer() as any
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
init();
|
||||||
requestService = getMockRequestService(getRequestEntry$(true));
|
requestService = getMockRequestService(getRequestEntry$(true));
|
||||||
store = new Store<CoreState>(undefined, undefined, undefined);
|
store = new Store<CoreState>(undefined, undefined, undefined);
|
||||||
service = initTestService();
|
service = initTestService();
|
||||||
|
@@ -181,7 +181,7 @@ export class EPersonDataService extends DataService<EPerson> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method that clears a cached EPerson request and returns its REST url
|
* Method that clears a cached EPerson request
|
||||||
*/
|
*/
|
||||||
public clearEPersonRequests(): void {
|
public clearEPersonRequests(): void {
|
||||||
this.getBrowseEndpoint().pipe(take(1)).subscribe((link: string) => {
|
this.getBrowseEndpoint().pipe(take(1)).subscribe((link: string) => {
|
||||||
@@ -189,6 +189,13 @@ export class EPersonDataService extends DataService<EPerson> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method that clears a link's requests in cache
|
||||||
|
*/
|
||||||
|
public clearLinkRequests(href: string): void {
|
||||||
|
this.requestService.removeByHrefSubstring(href);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to retrieve the eperson that is currently being edited
|
* Method to retrieve the eperson that is currently being edited
|
||||||
*/
|
*/
|
||||||
@@ -219,4 +226,27 @@ export class EPersonDataService extends DataService<EPerson> {
|
|||||||
return this.delete(ePerson.id);
|
return this.delete(ePerson.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change which ePerson is being edited and return the link for EPeople edit page
|
||||||
|
* @param ePerson New EPerson to edit
|
||||||
|
*/
|
||||||
|
public startEditingNewEPerson(ePerson: EPerson): string {
|
||||||
|
this.getActiveEPerson().pipe(take(1)).subscribe((activeEPerson: EPerson) => {
|
||||||
|
if (ePerson === activeEPerson) {
|
||||||
|
this.cancelEditEPerson();
|
||||||
|
} else {
|
||||||
|
this.editEPerson(ePerson);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return '/admin/access-control/epeople';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get EPeople admin page
|
||||||
|
* @param ePerson New EPerson to edit
|
||||||
|
*/
|
||||||
|
public getEPeoplePageRouterLink(): string {
|
||||||
|
return '/admin/access-control/epeople';
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
197
src/app/core/eperson/group-data.service.spec.ts
Normal file
197
src/app/core/eperson/group-data.service.spec.ts
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { HttpHeaders } from '@angular/common/http';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { Store, StoreModule } from '@ngrx/store';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { compare, Operation } from 'fast-json-patch';
|
||||||
|
import {
|
||||||
|
GroupRegistryCancelGroupAction,
|
||||||
|
GroupRegistryEditGroupAction
|
||||||
|
} from '../../+admin/admin-access-control/group-registry/group-registry.actions';
|
||||||
|
import { getMockRemoteDataBuildServiceHrefMap } from '../../shared/mocks/mock-remote-data-build.service';
|
||||||
|
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
|
||||||
|
import { MockTranslateLoader } from '../../shared/mocks/mock-translate-loader';
|
||||||
|
import { EPersonMock, EPersonMock2 } from '../../shared/testing/eperson-mock';
|
||||||
|
import { GroupMock, GroupMock2 } from '../../shared/testing/group-mock';
|
||||||
|
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
|
||||||
|
import { SearchParam } from '../cache/models/search-param.model';
|
||||||
|
import { CoreState } from '../core.reducers';
|
||||||
|
import { ChangeAnalyzer } from '../data/change-analyzer';
|
||||||
|
import { PaginatedList } from '../data/paginated-list';
|
||||||
|
import { DeleteByIDRequest, DeleteRequest, FindListOptions, PostRequest } from '../data/request.models';
|
||||||
|
import { RequestEntry } from '../data/request.reducer';
|
||||||
|
import { RequestService } from '../data/request.service';
|
||||||
|
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||||
|
import { Item } from '../shared/item.model';
|
||||||
|
import { PageInfo } from '../shared/page-info.model';
|
||||||
|
import { GroupDataService } from './group-data.service';
|
||||||
|
|
||||||
|
describe('GroupDataService', () => {
|
||||||
|
let service: GroupDataService;
|
||||||
|
let store: Store<CoreState>;
|
||||||
|
let requestService: RequestService;
|
||||||
|
|
||||||
|
let restEndpointURL;
|
||||||
|
let groupsEndpoint;
|
||||||
|
let groups;
|
||||||
|
let groups$;
|
||||||
|
let halService;
|
||||||
|
let rdbService;
|
||||||
|
|
||||||
|
let getRequestEntry$;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
getRequestEntry$ = (successful: boolean) => {
|
||||||
|
return observableOf({
|
||||||
|
completed: true,
|
||||||
|
response: { isSuccessful: successful, payload: groups } as any
|
||||||
|
} as RequestEntry)
|
||||||
|
};
|
||||||
|
restEndpointURL = 'https://dspace.4science.it/dspace-spring-rest/api/eperson';
|
||||||
|
groupsEndpoint = `${restEndpointURL}/groups`;
|
||||||
|
groups = [GroupMock, GroupMock2];
|
||||||
|
groups$ = createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), groups));
|
||||||
|
rdbService = getMockRemoteDataBuildServiceHrefMap(undefined, { 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups': groups$ });
|
||||||
|
halService = new HALEndpointServiceStub(restEndpointURL);
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
StoreModule.forRoot({}),
|
||||||
|
TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: MockTranslateLoader
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
declarations: [],
|
||||||
|
providers: [],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initTestService() {
|
||||||
|
return new GroupDataService(
|
||||||
|
new DummyChangeAnalyzer() as any,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
requestService,
|
||||||
|
rdbService,
|
||||||
|
store,
|
||||||
|
null,
|
||||||
|
halService
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
init();
|
||||||
|
requestService = getMockRequestService(getRequestEntry$(true));
|
||||||
|
store = new Store<CoreState>(undefined, undefined, undefined);
|
||||||
|
service = initTestService();
|
||||||
|
spyOn(store, 'dispatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('searchGroups', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, 'searchBy');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('search with empty query', () => {
|
||||||
|
service.searchGroups('');
|
||||||
|
const options = Object.assign(new FindListOptions(), {
|
||||||
|
searchParams: [Object.assign(new SearchParam('query', ''))]
|
||||||
|
});
|
||||||
|
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('search with query', () => {
|
||||||
|
service.searchGroups('test');
|
||||||
|
const options = Object.assign(new FindListOptions(), {
|
||||||
|
searchParams: [Object.assign(new SearchParam('query', 'test'))]
|
||||||
|
});
|
||||||
|
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deleteGroup', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service.deleteGroup(GroupMock2).subscribe();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send DeleteRequest', () => {
|
||||||
|
const expected = new DeleteByIDRequest(requestService.generateRequestId(), groupsEndpoint + '/' + GroupMock2.uuid, GroupMock2.uuid);
|
||||||
|
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('addSubGroupToGroup', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service.addSubGroupToGroup(GroupMock, GroupMock2).subscribe();
|
||||||
|
});
|
||||||
|
it('should send PostRequest to eperson/groups/group-id/subgroups endpoint with new subgroup link in body', () => {
|
||||||
|
let headers = new HttpHeaders();
|
||||||
|
const options: HttpOptions = Object.create({});
|
||||||
|
headers = headers.append('Content-Type', 'text/uri-list');
|
||||||
|
options.headers = headers;
|
||||||
|
const expected = new PostRequest(requestService.generateRequestId(), GroupMock.self + '/' + service.subgroupsEndpoint, GroupMock2.self, options);
|
||||||
|
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deleteSubGroupFromGroup', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service.deleteSubGroupFromGroup(GroupMock, GroupMock2).subscribe();
|
||||||
|
});
|
||||||
|
it('should send DeleteRequest to eperson/groups/group-id/subgroups/group-id endpoint', () => {
|
||||||
|
const expected = new DeleteRequest(requestService.generateRequestId(), GroupMock.self + '/' + service.subgroupsEndpoint + '/' + GroupMock2.id);
|
||||||
|
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('addMemberToGroup', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service.addMemberToGroup(GroupMock, EPersonMock2).subscribe();
|
||||||
|
});
|
||||||
|
it('should send PostRequest to eperson/groups/group-id/epersons endpoint with new eperson member in body', () => {
|
||||||
|
let headers = new HttpHeaders();
|
||||||
|
const options: HttpOptions = Object.create({});
|
||||||
|
headers = headers.append('Content-Type', 'text/uri-list');
|
||||||
|
options.headers = headers;
|
||||||
|
const expected = new PostRequest(requestService.generateRequestId(), GroupMock.self + '/' + service.ePersonsEndpoint, EPersonMock2.self, options);
|
||||||
|
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deleteMemberFromGroup', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service.deleteMemberFromGroup(GroupMock, EPersonMock).subscribe();
|
||||||
|
});
|
||||||
|
it('should send DeleteRequest to eperson/groups/group-id/epersons/eperson-id endpoint', () => {
|
||||||
|
const expected = new DeleteRequest(requestService.generateRequestId(), GroupMock.self + '/' + service.ePersonsEndpoint + '/' + EPersonMock.id);
|
||||||
|
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('editGroup', () => {
|
||||||
|
it('should dispatch a EDIT_GROUP action with the groupp to start editing', () => {
|
||||||
|
service.editGroup(GroupMock);
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith(new GroupRegistryEditGroupAction(GroupMock));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('cancelEditGroup', () => {
|
||||||
|
it('should dispatch a CANCEL_EDIT_GROUP action', () => {
|
||||||
|
service.cancelEditGroup();
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith(new GroupRegistryCancelGroupAction());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
class DummyChangeAnalyzer implements ChangeAnalyzer<Item> {
|
||||||
|
diff(object1: Item, object2: Item): Operation[] {
|
||||||
|
return compare((object1 as any).metadata, (object2 as any).metadata);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,28 +1,42 @@
|
|||||||
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
|
|
||||||
import { Store } from '@ngrx/store';
|
import { createSelector, select, Store } from '@ngrx/store';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { filter, map, take } from 'rxjs/operators';
|
import { filter, map, take } from 'rxjs/operators';
|
||||||
|
import {
|
||||||
|
GroupRegistryCancelGroupAction,
|
||||||
|
GroupRegistryEditGroupAction
|
||||||
|
} from '../../+admin/admin-access-control/group-registry/group-registry.actions';
|
||||||
|
import { GroupRegistryState } from '../../+admin/admin-access-control/group-registry/group-registry.reducers';
|
||||||
|
import { AppState } from '../../app.reducer';
|
||||||
|
import { hasValue } from '../../shared/empty.util';
|
||||||
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
|
import { SearchParam } from '../cache/models/search-param.model';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { RestResponse } from '../cache/response.models';
|
||||||
import { DataService } from '../data/data.service';
|
import { DataService } from '../data/data.service';
|
||||||
|
import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
|
||||||
|
import { PaginatedList } from '../data/paginated-list';
|
||||||
|
import { RemoteData } from '../data/remote-data';
|
||||||
|
import { DeleteRequest, FindListOptions, FindListRequest, PostRequest, RestRequest } from '../data/request.models';
|
||||||
|
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { FindListOptions } from '../data/request.models';
|
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
|
import { getResponseFromEntry } from '../shared/operators';
|
||||||
|
import { EPerson } from './models/eperson.model';
|
||||||
import { Group } from './models/group.model';
|
import { Group } from './models/group.model';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
|
||||||
import { CoreState } from '../core.reducers';
|
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
|
||||||
import { SearchParam } from '../cache/models/search-param.model';
|
|
||||||
import { RemoteData } from '../data/remote-data';
|
|
||||||
import { PaginatedList } from '../data/paginated-list';
|
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
|
||||||
import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
|
|
||||||
import { dataService } from '../cache/builders/build-decorators';
|
import { dataService } from '../cache/builders/build-decorators';
|
||||||
import { GROUP } from './models/group.resource-type';
|
import { GROUP } from './models/group.resource-type';
|
||||||
|
|
||||||
|
const groupRegistryStateSelector = (state: AppState) => state.groupRegistry;
|
||||||
|
const editGroupSelector = createSelector(groupRegistryStateSelector, (groupRegistryState: GroupRegistryState) => groupRegistryState.editGroup);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides methods to retrieve eperson group resources.
|
* Provides methods to retrieve eperson group resources from the REST API & Group related CRUD actions.
|
||||||
*/
|
*/
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@@ -31,6 +45,8 @@ import { GROUP } from './models/group.resource-type';
|
|||||||
export class GroupDataService extends DataService<Group> {
|
export class GroupDataService extends DataService<Group> {
|
||||||
protected linkPath = 'groups';
|
protected linkPath = 'groups';
|
||||||
protected browseEndpoint = '';
|
protected browseEndpoint = '';
|
||||||
|
public ePersonsEndpoint = 'epersons';
|
||||||
|
public subgroupsEndpoint = 'subgroups';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected comparator: DSOChangeAnalyzer<Group>,
|
protected comparator: DSOChangeAnalyzer<Group>,
|
||||||
@@ -38,13 +54,51 @@ export class GroupDataService extends DataService<Group> {
|
|||||||
protected notificationsService: NotificationsService,
|
protected notificationsService: NotificationsService,
|
||||||
protected requestService: RequestService,
|
protected requestService: RequestService,
|
||||||
protected rdbService: RemoteDataBuildService,
|
protected rdbService: RemoteDataBuildService,
|
||||||
protected store: Store<CoreState>,
|
protected store: Store<any>,
|
||||||
protected objectCache: ObjectCacheService,
|
protected objectCache: ObjectCacheService,
|
||||||
protected halService: HALEndpointService
|
protected halService: HALEndpointService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves all groups
|
||||||
|
* @param pagination The pagination info used to retrieve the groups
|
||||||
|
*/
|
||||||
|
public getGroups(options: FindListOptions = {}, ...linksToFollow: Array<FollowLinkConfig<Group>>): Observable<RemoteData<PaginatedList<Group>>> {
|
||||||
|
const hrefObs = this.getFindAllHref(options, this.linkPath, ...linksToFollow);
|
||||||
|
hrefObs.pipe(
|
||||||
|
filter((href: string) => hasValue(href)),
|
||||||
|
take(1))
|
||||||
|
.subscribe((href: string) => {
|
||||||
|
const request = new FindListRequest(this.requestService.generateRequestId(), href, options);
|
||||||
|
this.requestService.configure(request);
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.rdbService.buildList<Group>(hrefObs) as Observable<RemoteData<PaginatedList<Group>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a search result list of groups, with certain query (searches in group name and by exact uuid)
|
||||||
|
* Endpoint used: /eperson/groups/search/byMetadata?query=<:name>
|
||||||
|
* @param query search query param
|
||||||
|
* @param options
|
||||||
|
* @param linksToFollow
|
||||||
|
*/
|
||||||
|
public searchGroups(query: string, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<Group>>): Observable<RemoteData<PaginatedList<Group>>> {
|
||||||
|
const searchParams = [new SearchParam('query', query)];
|
||||||
|
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('byMetadata', findListOptions, ...linksToFollow);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the current user is member of to the indicated group
|
* Check if the current user is member of to the indicated group
|
||||||
*
|
*
|
||||||
@@ -65,4 +119,194 @@ export class GroupDataService extends DataService<Group> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to delete a group
|
||||||
|
* @param id The group id to delete
|
||||||
|
*/
|
||||||
|
public deleteGroup(group: Group): Observable<boolean> {
|
||||||
|
return this.delete(group.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create or Update a group
|
||||||
|
* If the group contains an id, it is assumed the eperson already exists and is updated instead
|
||||||
|
* @param group The group to create or update
|
||||||
|
*/
|
||||||
|
public createOrUpdateGroup(group: Group): Observable<RemoteData<Group>> {
|
||||||
|
const isUpdate = hasValue(group.id);
|
||||||
|
if (isUpdate) {
|
||||||
|
return this.updateGroup(group);
|
||||||
|
} else {
|
||||||
|
return this.create(group, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* // TODO
|
||||||
|
* @param {DSpaceObject} ePerson The given object
|
||||||
|
*/
|
||||||
|
updateGroup(group: Group): Observable<RemoteData<Group>> {
|
||||||
|
// TODO
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds given subgroup as a subgroup to the given active group
|
||||||
|
* @param activeGroup Group we want to add subgroup to
|
||||||
|
* @param subgroup Group we want to add as subgroup to activeGroup
|
||||||
|
*/
|
||||||
|
addSubGroupToGroup(activeGroup: Group, subgroup: Group): Observable<RestResponse> {
|
||||||
|
const requestId = this.requestService.generateRequestId();
|
||||||
|
const options: HttpOptions = Object.create({});
|
||||||
|
let headers = new HttpHeaders();
|
||||||
|
headers = headers.append('Content-Type', 'text/uri-list');
|
||||||
|
options.headers = headers;
|
||||||
|
const postRequest = new PostRequest(requestId, activeGroup.self + '/' + this.subgroupsEndpoint, subgroup.self, options);
|
||||||
|
this.requestService.configure(postRequest);
|
||||||
|
|
||||||
|
return this.fetchResponse(requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a given subgroup from the subgroups of the given active group
|
||||||
|
* @param activeGroup Group we want to delete subgroup from
|
||||||
|
* @param subgroup Subgroup we want to delete from activeGroup
|
||||||
|
*/
|
||||||
|
deleteSubGroupFromGroup(activeGroup: Group, subgroup: Group): Observable<RestResponse> {
|
||||||
|
const requestId = this.requestService.generateRequestId();
|
||||||
|
const deleteRequest = new DeleteRequest(requestId, activeGroup.self + '/' + this.subgroupsEndpoint + '/' + subgroup.id);
|
||||||
|
this.requestService.configure(deleteRequest);
|
||||||
|
|
||||||
|
return this.fetchResponse(requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds given ePerson as member to given group
|
||||||
|
* @param activeGroup Group we want to add member to
|
||||||
|
* @param ePerson EPerson we want to add as member to given activeGroup
|
||||||
|
*/
|
||||||
|
addMemberToGroup(activeGroup: Group, ePerson: EPerson): Observable<RestResponse> {
|
||||||
|
const requestId = this.requestService.generateRequestId();
|
||||||
|
const options: HttpOptions = Object.create({});
|
||||||
|
let headers = new HttpHeaders();
|
||||||
|
headers = headers.append('Content-Type', 'text/uri-list');
|
||||||
|
options.headers = headers;
|
||||||
|
const postRequest = new PostRequest(requestId, activeGroup.self + '/' + this.ePersonsEndpoint, ePerson.self, options);
|
||||||
|
this.requestService.configure(postRequest);
|
||||||
|
|
||||||
|
return this.fetchResponse(requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a given ePerson from the members of the given active group
|
||||||
|
* @param activeGroup Group we want to delete member from
|
||||||
|
* @param ePerson EPerson we want to delete from members of given activeGroup
|
||||||
|
*/
|
||||||
|
deleteMemberFromGroup(activeGroup: Group, ePerson: EPerson): Observable<RestResponse> {
|
||||||
|
const requestId = this.requestService.generateRequestId();
|
||||||
|
const deleteRequest = new DeleteRequest(requestId, activeGroup.self + '/' + this.ePersonsEndpoint + '/' + ePerson.id);
|
||||||
|
this.requestService.configure(deleteRequest);
|
||||||
|
|
||||||
|
return this.fetchResponse(requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the restResponse from the requestService
|
||||||
|
* @param requestId
|
||||||
|
*/
|
||||||
|
protected fetchResponse(requestId: string): Observable<RestResponse> {
|
||||||
|
return this.requestService.getByUUID(requestId).pipe(
|
||||||
|
getResponseFromEntry(),
|
||||||
|
map((response: RestResponse) => {
|
||||||
|
return response;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to retrieve the group that is currently being edited
|
||||||
|
*/
|
||||||
|
public getActiveGroup(): Observable<Group> {
|
||||||
|
return this.store.pipe(select(editGroupSelector))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to cancel editing a group, dispatches a cancel group action
|
||||||
|
*/
|
||||||
|
public cancelEditGroup() {
|
||||||
|
this.store.dispatch(new GroupRegistryCancelGroupAction());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to set the group being edited, dispatches an edit group action
|
||||||
|
* @param group The group to edit
|
||||||
|
*/
|
||||||
|
public editGroup(group: Group) {
|
||||||
|
this.store.dispatch(new GroupRegistryEditGroupAction(group));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method that clears a cached groups request
|
||||||
|
*/
|
||||||
|
public clearGroupsRequests(): void {
|
||||||
|
this.getBrowseEndpoint().pipe(take(1)).subscribe((link: string) => {
|
||||||
|
this.requestService.removeByHrefSubstring(link);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method that clears a cached get subgroups of certain group request
|
||||||
|
*/
|
||||||
|
public clearGroupLinkRequests(href: string): void {
|
||||||
|
this.requestService.removeByHrefSubstring(href);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getGroupRegistryRouterLink(): string {
|
||||||
|
return '/admin/access-control/groups';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change which group is being edited and return the link for the edit page of the new group being edited
|
||||||
|
* @param newGroup New group to edit
|
||||||
|
*/
|
||||||
|
public startEditingNewGroup(newGroup: Group): string {
|
||||||
|
this.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => {
|
||||||
|
if (newGroup === activeGroup) {
|
||||||
|
this.cancelEditGroup()
|
||||||
|
} else {
|
||||||
|
this.editGroup(newGroup)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this.getGroupEditPageRouterLinkWithID(newGroup.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Edit page of group
|
||||||
|
* @param group Group we want edit page for
|
||||||
|
*/
|
||||||
|
public getGroupEditPageRouterLink(group: Group): string {
|
||||||
|
return this.getGroupEditPageRouterLinkWithID(group.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Edit page of group
|
||||||
|
* @param groupID Group ID we want edit page for
|
||||||
|
*/
|
||||||
|
public getGroupEditPageRouterLinkWithID(groupId: string): string {
|
||||||
|
return '/admin/access-control/groups/' + groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract optional UUID from a string
|
||||||
|
* @param stringWithUUID String with possible UUID
|
||||||
|
*/
|
||||||
|
public getUUIDFromString(stringWithUUID: string): string {
|
||||||
|
let foundUUID = '';
|
||||||
|
const uuidMatches = stringWithUUID.match(/[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}/g);
|
||||||
|
if (uuidMatches != null) {
|
||||||
|
foundUUID = uuidMatches[0];
|
||||||
|
}
|
||||||
|
return foundUUID;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,8 @@ import { RemoteData } from '../../data/remote-data';
|
|||||||
|
|
||||||
import { DSpaceObject } from '../../shared/dspace-object.model';
|
import { DSpaceObject } from '../../shared/dspace-object.model';
|
||||||
import { HALLink } from '../../shared/hal-link.model';
|
import { HALLink } from '../../shared/hal-link.model';
|
||||||
|
import { EPerson } from './eperson.model';
|
||||||
|
import { EPERSON } from './eperson.resource-type';
|
||||||
import { GROUP } from './group.resource-type';
|
import { GROUP } from './group.resource-type';
|
||||||
|
|
||||||
@typedObject
|
@typedObject
|
||||||
@@ -13,6 +15,12 @@ import { GROUP } from './group.resource-type';
|
|||||||
export class Group extends DSpaceObject {
|
export class Group extends DSpaceObject {
|
||||||
static type = GROUP;
|
static type = GROUP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A string representing the unique name of this Group
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
public name: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A string representing the unique handle of this Group
|
* A string representing the unique handle of this Group
|
||||||
*/
|
*/
|
||||||
@@ -31,7 +39,8 @@ export class Group extends DSpaceObject {
|
|||||||
@deserialize
|
@deserialize
|
||||||
_links: {
|
_links: {
|
||||||
self: HALLink;
|
self: HALLink;
|
||||||
groups: HALLink;
|
subgroups: HALLink;
|
||||||
|
epersons: HALLink;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,6 +48,13 @@ export class Group extends DSpaceObject {
|
|||||||
* Will be undefined unless the groups {@link HALLink} has been resolved.
|
* Will be undefined unless the groups {@link HALLink} has been resolved.
|
||||||
*/
|
*/
|
||||||
@link(GROUP, true)
|
@link(GROUP, true)
|
||||||
public groups?: Observable<RemoteData<PaginatedList<Group>>>;
|
public subgroups?: Observable<RemoteData<PaginatedList<Group>>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of EPeople in this group
|
||||||
|
* Will be undefined unless the epersons {@link HALLink} has been resolved.
|
||||||
|
*/
|
||||||
|
@link(EPERSON, true)
|
||||||
|
public epersons?: Observable<RemoteData<PaginatedList<EPerson>>>;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@ import { GroupMock } from './group-mock';
|
|||||||
|
|
||||||
export const EPersonMock: EPerson = Object.assign(new EPerson(), {
|
export const EPersonMock: EPerson = Object.assign(new EPerson(), {
|
||||||
handle: null,
|
handle: null,
|
||||||
groups: [],
|
groups: [GroupMock],
|
||||||
netid: 'test@test.com',
|
netid: 'test@test.com',
|
||||||
lastActive: '2018-05-14T12:25:42.411+0000',
|
lastActive: '2018-05-14T12:25:42.411+0000',
|
||||||
canLogIn: true,
|
canLogIn: true,
|
||||||
@@ -49,7 +49,7 @@ export const EPersonMock: EPerson = Object.assign(new EPerson(), {
|
|||||||
|
|
||||||
export const EPersonMock2: EPerson = Object.assign(new EPerson(), {
|
export const EPersonMock2: EPerson = Object.assign(new EPerson(), {
|
||||||
handle: null,
|
handle: null,
|
||||||
groups: [GroupMock],
|
groups: [],
|
||||||
netid: 'test2@test.com',
|
netid: 'test2@test.com',
|
||||||
lastActive: '2019-05-14T12:25:42.411+0000',
|
lastActive: '2019-05-14T12:25:42.411+0000',
|
||||||
canLogIn: false,
|
canLogIn: false,
|
||||||
|
@@ -1,14 +1,36 @@
|
|||||||
import { Group } from '../../core/eperson/models/group.model';
|
import { Group } from '../../core/eperson/models/group.model';
|
||||||
|
import { EPersonMock } from './eperson-mock';
|
||||||
|
|
||||||
|
export const GroupMock2: Group = Object.assign(new Group(), {
|
||||||
|
handle: null,
|
||||||
|
subgroups: [],
|
||||||
|
epersons: [],
|
||||||
|
permanent: true,
|
||||||
|
selfRegistered: false,
|
||||||
|
_links: {
|
||||||
|
self: {
|
||||||
|
href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2',
|
||||||
|
},
|
||||||
|
subgroups: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/subgroups' },
|
||||||
|
epersons: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/epersons' }
|
||||||
|
},
|
||||||
|
id: 'testgroupid2',
|
||||||
|
uuid: 'testgroupid2',
|
||||||
|
type: 'group',
|
||||||
|
});
|
||||||
|
|
||||||
export const GroupMock: Group = Object.assign(new Group(), {
|
export const GroupMock: Group = Object.assign(new Group(), {
|
||||||
handle: null,
|
handle: null,
|
||||||
groups: [],
|
subgroups: [GroupMock2],
|
||||||
|
epersons: [EPersonMock],
|
||||||
selfRegistered: false,
|
selfRegistered: false,
|
||||||
|
permanent: false,
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid',
|
href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid',
|
||||||
},
|
},
|
||||||
groups: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/groups' }
|
subgroups: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/subgroups' },
|
||||||
|
epersons: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/epersons' }
|
||||||
},
|
},
|
||||||
id: 'testgroupid',
|
id: 'testgroupid',
|
||||||
uuid: 'testgroupid',
|
uuid: 'testgroupid',
|
||||||
|
@@ -2,7 +2,6 @@ import { ChangeDetectorRef, Component, Inject } from '@angular/core';
|
|||||||
|
|
||||||
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, Subscription} from 'rxjs';
|
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, Subscription} from 'rxjs';
|
||||||
import { distinctUntilChanged, filter, find, flatMap, map, reduce, take, tap } from 'rxjs/operators';
|
import { distinctUntilChanged, filter, find, flatMap, map, reduce, take, tap } from 'rxjs/operators';
|
||||||
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
|
||||||
|
|
||||||
import { SectionModelComponent } from '../models/section.model';
|
import { SectionModelComponent } from '../models/section.model';
|
||||||
import { hasValue, isNotEmpty, isNotUndefined, isUndefined } from '../../../shared/empty.util';
|
import { hasValue, isNotEmpty, isNotUndefined, isUndefined } from '../../../shared/empty.util';
|
||||||
@@ -23,7 +22,6 @@ import { Group } from '../../../core/eperson/models/group.model';
|
|||||||
import { SectionsService } from '../sections.service';
|
import { SectionsService } from '../sections.service';
|
||||||
import { SubmissionService } from '../../submission.service';
|
import { SubmissionService } from '../../submission.service';
|
||||||
import { Collection } from '../../../core/shared/collection.model';
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
import { ResourcePolicy } from '../../../core/shared/resource-policy.model';
|
|
||||||
import { AccessConditionOption } from '../../../core/config/models/config-access-condition-option.model';
|
import { AccessConditionOption } from '../../../core/config/models/config-access-condition-option.model';
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
|
|
||||||
@@ -205,7 +203,7 @@ export class SubmissionSectionUploadComponent extends SectionModelComponent {
|
|||||||
mapGroups$.push(
|
mapGroups$.push(
|
||||||
this.groupService.findById(accessCondition.selectGroupUUID).pipe(
|
this.groupService.findById(accessCondition.selectGroupUUID).pipe(
|
||||||
find((rd: RemoteData<Group>) => !rd.isResponsePending && rd.hasSucceeded),
|
find((rd: RemoteData<Group>) => !rd.isResponsePending && rd.hasSucceeded),
|
||||||
flatMap((group: RemoteData<Group>) => group.payload.groups),
|
flatMap((group: RemoteData<Group>) => group.payload.subgroups),
|
||||||
find((rd: RemoteData<PaginatedList<Group>>) => !rd.isResponsePending && rd.hasSucceeded),
|
find((rd: RemoteData<PaginatedList<Group>>) => !rd.isResponsePending && rd.hasSucceeded),
|
||||||
map((rd: RemoteData<PaginatedList<Group>>) => ({
|
map((rd: RemoteData<PaginatedList<Group>>) => ({
|
||||||
accessCondition: accessCondition.name,
|
accessCondition: accessCondition.name,
|
||||||
|
Reference in New Issue
Block a user