mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-09 11:03:05 +00:00
69111: Groups admin page, WIP
This commit is contained in:
@@ -176,12 +176,10 @@
|
|||||||
|
|
||||||
"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.search.scope.metadata": "Metadata",
|
||||||
|
|
||||||
"admin.access-control.epeople.search.scope.email": "E-mail (exact)",
|
"admin.access-control.epeople.search.scope.email": "E-mail (exact)",
|
||||||
|
|
||||||
"admin.access-control.epeople.search.scope.metadata": "Metadata",
|
|
||||||
|
|
||||||
"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",
|
||||||
@@ -233,6 +231,96 @@
|
|||||||
"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.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.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}}\"",
|
||||||
|
// TODO
|
||||||
|
"admin.access-control.groups.notification.deleted.failure": "Failed to delete group \"{{name}}\" (Not yet implemented)",
|
||||||
|
|
||||||
|
|
||||||
|
"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.button.return": "Return",
|
||||||
|
|
||||||
|
"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.members-list.head": "Members",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.members-list.search.head": "Search EPeople",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.members-list.search.scope.metadata": "Metadata",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.members-list.search.scope.email": "E-mail",
|
||||||
|
|
||||||
|
"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.table.edit.buttons.add": "Add member with name \"{{name}}\"",
|
||||||
|
|
||||||
|
"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.button.see-all": "Search all",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.members-list.no-items": "No EPeople found in that search",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.subgroups-list.head": "Subgroups",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.subgroups-list.search.head": "Search Groups",
|
||||||
|
|
||||||
|
"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.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, search and add.",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.subgroups-list.button.see-all": "Search all",
|
||||||
|
|
||||||
|
"admin.access-control.groups.form.return": "Return",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"admin.search.breadcrumbs": "Administrative Search",
|
"admin.search.breadcrumbs": "Administrative Search",
|
||||||
|
|
||||||
|
@@ -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: []
|
||||||
})
|
})
|
||||||
|
@@ -21,143 +21,143 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio
|
|||||||
*/
|
*/
|
||||||
export class EPeopleRegistryComponent {
|
export class EPeopleRegistryComponent {
|
||||||
|
|
||||||
labelPrefix = 'admin.access-control.epeople.';
|
labelPrefix = 'admin.access-control.epeople.';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of all the current EPeople within the repository or the result of the search
|
* A list of all the current EPeople within the repository or the result of the search
|
||||||
*/
|
*/
|
||||||
ePeople: Observable<RemoteData<PaginatedList<EPerson>>>;
|
ePeople: Observable<RemoteData<PaginatedList<EPerson>>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pagination config used to display the list of epeople
|
* Pagination config used to display the list of epeople
|
||||||
*/
|
*/
|
||||||
config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
|
config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
|
||||||
id: 'epeople-list-pagination',
|
id: 'epeople-list-pagination',
|
||||||
pageSize: 5,
|
pageSize: 5,
|
||||||
currentPage: 1
|
currentPage: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not to show the EPerson form
|
||||||
|
*/
|
||||||
|
isEPersonFormShown: boolean;
|
||||||
|
|
||||||
|
// The search form
|
||||||
|
searchForm;
|
||||||
|
|
||||||
|
constructor(private epersonService: EPersonDataService,
|
||||||
|
private translateService: TranslateService,
|
||||||
|
private notificationsService: NotificationsService,
|
||||||
|
private formBuilder: FormBuilder) {
|
||||||
|
this.updateEPeople({
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: this.config.pageSize
|
||||||
});
|
});
|
||||||
|
this.isEPersonFormShown = false;
|
||||||
|
this.searchForm = this.formBuilder.group(({
|
||||||
|
scope: 'metadata',
|
||||||
|
query: '',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not to show the EPerson form
|
* Event triggered when the user changes page
|
||||||
*/
|
* @param event
|
||||||
isEPersonFormShown: boolean;
|
*/
|
||||||
|
onPageChange(event) {
|
||||||
|
this.updateEPeople({
|
||||||
|
currentPage: event,
|
||||||
|
elementsPerPage: this.config.pageSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// The search form
|
/**
|
||||||
searchForm;
|
* Update the list of EPeople by fetching it from the rest api or cache
|
||||||
|
*/
|
||||||
|
private updateEPeople(options) {
|
||||||
|
this.ePeople = this.epersonService.getEPeople(options);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(private epersonService: EPersonDataService,
|
/**
|
||||||
private translateService: TranslateService,
|
* Force-update the list of EPeople by first clearing the cache related to EPeople, then performing
|
||||||
private notificationsService: NotificationsService,
|
* a new REST call
|
||||||
private formBuilder: FormBuilder) {
|
*/
|
||||||
this.updateEPeople({
|
public forceUpdateEPeople() {
|
||||||
currentPage: 1,
|
this.epersonService.clearEPersonRequests();
|
||||||
elementsPerPage: this.config.pageSize
|
this.isEPersonFormShown = false;
|
||||||
});
|
this.search({ query: '', scope: 'metadata' })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search in the EPeople by metadata (default) or email
|
||||||
|
* @param data Contains scope and query param
|
||||||
|
*/
|
||||||
|
search(data: any) {
|
||||||
|
this.ePeople = this.epersonService.searchByScope(data.scope, data.query, {
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: this.config.pageSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the given EPerson is active (being edited)
|
||||||
|
* @param eperson
|
||||||
|
*/
|
||||||
|
isActive(eperson: EPerson): Observable<boolean> {
|
||||||
|
return this.getActiveEPerson().pipe(
|
||||||
|
map((activeEPerson) => eperson === activeEPerson)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the active eperson (being edited)
|
||||||
|
*/
|
||||||
|
getActiveEPerson(): Observable<EPerson> {
|
||||||
|
return this.epersonService.getActiveEPerson();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start editing the selected EPerson
|
||||||
|
* @param ePerson
|
||||||
|
*/
|
||||||
|
toggleEditEPerson(ePerson: EPerson) {
|
||||||
|
this.getActiveEPerson().pipe(take(1)).subscribe((activeEPerson: EPerson) => {
|
||||||
|
if (ePerson === activeEPerson) {
|
||||||
|
this.epersonService.cancelEditEPerson();
|
||||||
this.isEPersonFormShown = false;
|
this.isEPersonFormShown = false;
|
||||||
this.searchForm = this.formBuilder.group(({
|
} else {
|
||||||
scope: 'metadata',
|
this.epersonService.editEPerson(ePerson);
|
||||||
query: '',
|
this.isEPersonFormShown = true;
|
||||||
}));
|
}
|
||||||
}
|
});
|
||||||
|
this.scrollToTop()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event triggered when the user changes page
|
* Deletes EPerson, show notification on success/failure & updates EPeople list
|
||||||
* @param event
|
*/
|
||||||
*/
|
deleteEPerson(ePerson: EPerson) {
|
||||||
onPageChange(event) {
|
if (hasValue(ePerson.id)) {
|
||||||
this.updateEPeople({
|
this.epersonService.deleteEPerson(ePerson).pipe(take(1)).subscribe((success: boolean) => {
|
||||||
currentPage: event,
|
if (success) {
|
||||||
elementsPerPage: this.config.pageSize
|
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', { name: ePerson.name }));
|
||||||
});
|
this.forceUpdateEPeople();
|
||||||
}
|
} else {
|
||||||
|
this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.deleted.failure', { name: ePerson.name }));
|
||||||
/**
|
|
||||||
* Update the list of EPeople by fetching it from the rest api or cache
|
|
||||||
*/
|
|
||||||
private updateEPeople(options) {
|
|
||||||
this.ePeople = this.epersonService.getEPeople(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Force-update the list of EPeople by first clearing the cache related to EPeople, then performing
|
|
||||||
* a new REST call
|
|
||||||
*/
|
|
||||||
public forceUpdateEPeople() {
|
|
||||||
this.epersonService.clearEPersonRequests();
|
|
||||||
this.isEPersonFormShown = false;
|
|
||||||
this.search({ query: '', scope: 'metadata' })
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search in the EPeople by metadata (default) or email
|
|
||||||
* @param data Contains scope and query param
|
|
||||||
*/
|
|
||||||
search(data: any) {
|
|
||||||
this.ePeople = this.epersonService.searchByScope(data.scope, data.query, {
|
|
||||||
currentPage: 1,
|
|
||||||
elementsPerPage: this.config.pageSize
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether the given EPerson is active (being edited)
|
|
||||||
* @param eperson
|
|
||||||
*/
|
|
||||||
isActive(eperson: EPerson): Observable<boolean> {
|
|
||||||
return this.getActiveEPerson().pipe(
|
|
||||||
map((activeEPerson) => eperson === activeEPerson)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the active eperson (being edited)
|
|
||||||
*/
|
|
||||||
getActiveEPerson(): Observable<EPerson> {
|
|
||||||
return this.epersonService.getActiveEPerson();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start editing the selected EPerson
|
|
||||||
* @param ePerson
|
|
||||||
*/
|
|
||||||
toggleEditEPerson(ePerson: EPerson) {
|
|
||||||
this.getActiveEPerson().pipe(take(1)).subscribe((activeEPerson: EPerson) => {
|
|
||||||
if (ePerson === activeEPerson) {
|
|
||||||
this.epersonService.cancelEditEPerson();
|
|
||||||
this.isEPersonFormShown = false;
|
|
||||||
} else {
|
|
||||||
this.epersonService.editEPerson(ePerson);
|
|
||||||
this.isEPersonFormShown = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.scrollToTop()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes EPerson, show notification on success/failure & updates EPeople list
|
|
||||||
*/
|
|
||||||
deleteEPerson(ePerson: EPerson) {
|
|
||||||
if (hasValue(ePerson.id)) {
|
|
||||||
this.epersonService.deleteEPerson(ePerson).pipe(take(1)).subscribe((success: boolean) => {
|
|
||||||
if (success) {
|
|
||||||
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', { name: ePerson.name }));
|
|
||||||
this.forceUpdateEPeople();
|
|
||||||
} else {
|
|
||||||
this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.deleted.failure', { name: ePerson.name }));
|
|
||||||
}
|
|
||||||
this.epersonService.cancelEditEPerson();
|
|
||||||
this.isEPersonFormShown = false;
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
this.epersonService.cancelEditEPerson();
|
||||||
|
this.isEPersonFormShown = false;
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
scrollToTop() {
|
scrollToTop() {
|
||||||
(function smoothscroll() {
|
(function smoothscroll() {
|
||||||
const currentScroll = document.documentElement.scrollTop || document.body.scrollTop;
|
const currentScroll = document.documentElement.scrollTop || document.body.scrollTop;
|
||||||
if (currentScroll > 0) {
|
if (currentScroll > 0) {
|
||||||
window.requestAnimationFrame(smoothscroll);
|
window.requestAnimationFrame(smoothscroll);
|
||||||
window.scrollTo(0, currentScroll - (currentScroll / 8));
|
window.scrollTo(0, currentScroll - (currentScroll / 8));
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -241,7 +241,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);
|
||||||
|
@@ -0,0 +1,32 @@
|
|||||||
|
<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-subgroups-list [messagePrefix]="messagePrefix + '.subgroups-list'"></ds-subgroups-list>
|
||||||
|
<ds-members-list [messagePrefix]="messagePrefix + '.members-list'"></ds-members-list>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button [routerLink]="['/admin/access-control/groups']" class="btn btn-primary">{{messagePrefix + '.return' | translate}}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,227 @@
|
|||||||
|
import { Component, EventEmitter, 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 { 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 } 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[] = [];
|
||||||
|
|
||||||
|
constructor(public groupDataService: GroupDataService,
|
||||||
|
private ePersonDataService: EPersonDataService,
|
||||||
|
private formBuilderService: FormBuilderService,
|
||||||
|
private translateService: TranslateService,
|
||||||
|
private notificationsService: NotificationsService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
protected router: Router) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
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: 'description',
|
||||||
|
label: groupDescription,
|
||||||
|
name: 'dc.description',
|
||||||
|
required: false,
|
||||||
|
});
|
||||||
|
this.formModel = [
|
||||||
|
this.groupName,
|
||||||
|
];
|
||||||
|
this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
|
||||||
|
this.subs.push(this.groupDataService.getActiveGroup().subscribe((group: Group) => {
|
||||||
|
this.formGroup.patchValue({
|
||||||
|
groupName: group != null ? group.name : '',
|
||||||
|
groupDescription: group != null ? group.firstMetadataValue('dc.description') : '',
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop editing the currently selected group
|
||||||
|
*/
|
||||||
|
onCancel() {
|
||||||
|
this.groupDataService.cancelEditGroup();
|
||||||
|
this.cancelForm.emit();
|
||||||
|
this.router.navigate(['/admin/access-control/groups']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
this.clearFields();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new Group based on given values from form
|
||||||
|
* @param values
|
||||||
|
*/
|
||||||
|
createNewGroup(values) {
|
||||||
|
this.subs.push(this.groupDataService.createOrUpdateGroup(Object.assign(new Group(), values))
|
||||||
|
.pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload())
|
||||||
|
.subscribe((group: Group) => {
|
||||||
|
this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.created.success', { name: group.name }));
|
||||||
|
this.submitForm.emit(group);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* //TODO
|
||||||
|
* @param group
|
||||||
|
* @param values
|
||||||
|
*/
|
||||||
|
editGroup(group: Group, values) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset all input-fields to be empty
|
||||||
|
*/
|
||||||
|
clearFields() {
|
||||||
|
this.formGroup.patchValue({
|
||||||
|
groupName: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start editing the selected group
|
||||||
|
* @param group
|
||||||
|
*/
|
||||||
|
setActiveGroup(groupId: string) {
|
||||||
|
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => {
|
||||||
|
if (activeGroup === null) {
|
||||||
|
this.groupDataService.cancelEditGroup();
|
||||||
|
this.groupDataService.findById(groupId)
|
||||||
|
.pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload())
|
||||||
|
.subscribe((group: Group) => {
|
||||||
|
this.groupDataService.editGroup(group);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel the current edit when component is destroyed & unsub all subscriptions
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.onCancel();
|
||||||
|
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,78 @@
|
|||||||
|
<ng-container>
|
||||||
|
<h3 class="border-bottom pb-2">{{messagePrefix + '.head' | translate}}</h3>
|
||||||
|
|
||||||
|
<h4 id="search" class="border-bottom pb-2">{{messagePrefix + '.search.head' | translate}}</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="(ePeople | async)?.payload.totalElements > 0"
|
||||||
|
[paginationOptions]="config"
|
||||||
|
[pageInfoState]="(ePeople | async)?.payload"
|
||||||
|
[collectionSize]="(ePeople | 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>{{messagePrefix + '.table.edit' | translate}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let ePerson of (ePeople | async)?.payload?.page">
|
||||||
|
<td>{{ePerson.id}}</td>
|
||||||
|
<td>{{ePerson.name}}</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="(ePeople | async)?.payload.totalElements == 0 && !searchDone" class="alert alert-info w-100 mb-2" role="alert">
|
||||||
|
{{messagePrefix + '.no-members-yet' | translate}}
|
||||||
|
<button (click)="search({query: ''})" class="btn btn-primary">{{messagePrefix + '.button.see-all' | translate}}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="(ePeople | async)?.payload.totalElements == 0 && searchDone" class="alert alert-info w-100 mb-2" role="alert">
|
||||||
|
{{messagePrefix + '.no-items' | translate}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</ng-container>
|
@@ -0,0 +1,152 @@
|
|||||||
|
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { FormBuilder } from '@angular/forms';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { Observable, of as observableOf, Subscription } from 'rxjs';
|
||||||
|
import { map, mergeMap, 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 { 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: Observable<RemoteData<PaginatedList<EPerson>>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pagination config used to display the list of EPeople
|
||||||
|
*/
|
||||||
|
config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
|
||||||
|
id: 'members-list-pagination',
|
||||||
|
pageSize: 5,
|
||||||
|
currentPage: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of subscriptions
|
||||||
|
*/
|
||||||
|
subs: Subscription[] = [];
|
||||||
|
|
||||||
|
// The search form
|
||||||
|
searchForm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not user has done a search yet
|
||||||
|
*/
|
||||||
|
searchDone: boolean;
|
||||||
|
|
||||||
|
constructor(private groupDataService: GroupDataService,
|
||||||
|
private ePersonDataService: EPersonDataService,
|
||||||
|
private translateService: TranslateService,
|
||||||
|
private notificationsService: NotificationsService,
|
||||||
|
private formBuilder: FormBuilder) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.subs.push(this.groupDataService.getActiveGroup().subscribe((group: Group) => {
|
||||||
|
if (group != null) {
|
||||||
|
this.ePeople = this.ePersonDataService.findAllByHref(group._links.epersons.href, {
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: this.config.pageSize
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
this.searchForm = this.formBuilder.group(({
|
||||||
|
scope: 'metadata',
|
||||||
|
query: '',
|
||||||
|
}));
|
||||||
|
this.searchDone = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event triggered when the user changes page
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
onPageChange(event) {
|
||||||
|
this.updateMembers({
|
||||||
|
currentPage: event,
|
||||||
|
elementsPerPage: this.config.pageSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the list of members by fetching it from the rest api or cache
|
||||||
|
*/
|
||||||
|
private updateMembers(options) {
|
||||||
|
this.ePeople = this.ePersonDataService.getEPeople(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteMemberFromGroup(ePerson: EPerson) {
|
||||||
|
// TODO
|
||||||
|
console.log('deleteMember TODO', ePerson);
|
||||||
|
// this.forceUpdateEPeople();
|
||||||
|
}
|
||||||
|
|
||||||
|
addMemberToGroup(ePerson: EPerson) {
|
||||||
|
// TODO
|
||||||
|
console.log('addMember TODO', ePerson);
|
||||||
|
// this.forceUpdateEPeople();
|
||||||
|
}
|
||||||
|
|
||||||
|
isMemberOfGroup(ePerson: EPerson): Observable<boolean> {
|
||||||
|
return this.groupDataService.getActiveGroup().pipe(take(1),
|
||||||
|
mergeMap((group: Group) => {
|
||||||
|
if (group != null) {
|
||||||
|
return this.groupDataService.findAllByHref(ePerson._links.groups.href, {
|
||||||
|
currentPage: 0,
|
||||||
|
elementsPerPage: Number.MAX_SAFE_INTEGER
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
map((listTotalGroups: PaginatedList<Group>) => listTotalGroups.page.filter((groupInList: Group) => groupInList.id === group.id)),
|
||||||
|
map((groups: Group[]) => groups.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) {
|
||||||
|
this.searchDone = true;
|
||||||
|
this.ePeople = this.ePersonDataService.searchByScope(data.scope, data.query, {
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: this.config.pageSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force-update the list of EPeople by first clearing the cache related to EPeople, then performing
|
||||||
|
* a new REST call
|
||||||
|
*/
|
||||||
|
public forceUpdateEPeople() {
|
||||||
|
this.ePersonDataService.clearEPersonRequests();
|
||||||
|
this.search({ query: '', scope: 'metadata' })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* unsub all subscriptions
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,72 @@
|
|||||||
|
<ng-container>
|
||||||
|
<h3 class="border-bottom pb-2">{{messagePrefix + '.head' | translate}}</h3>
|
||||||
|
|
||||||
|
<h4 id="search" class="border-bottom pb-2">{{messagePrefix + '.search.head' | translate}}</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="(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>{{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>
|
||||||
|
<div class="btn-group edit-field">
|
||||||
|
<button *ngIf="(isSubgroupOfGroup(group) | async)"
|
||||||
|
(click)="deleteSubgroupFromGroup(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>
|
||||||
|
|
||||||
|
<button *ngIf="!(isSubgroupOfGroup(group) | async)"
|
||||||
|
(click)="addSubgroupToGroup(group)"
|
||||||
|
class="btn btn-outline-primary btn-sm"
|
||||||
|
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="(groups | async)?.payload.totalElements == 0 && !searchDone" class="alert alert-info w-100 mb-2" role="alert">
|
||||||
|
{{messagePrefix + '.no-subgroups-yet' | translate}}
|
||||||
|
<button (click)="search({query: ''})" class="btn btn-primary">{{messagePrefix + '.button.see-all' | translate}}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="(groups | async)?.payload.totalElements == 0 && searchDone" class="alert alert-info w-100 mb-2" role="alert">
|
||||||
|
{{messagePrefix + '.no-items' | translate}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</ng-container>
|
@@ -0,0 +1,149 @@
|
|||||||
|
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { FormBuilder } from '@angular/forms';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { Observable, of as observableOf, Subscription } from 'rxjs';
|
||||||
|
import { map, mergeMap, take } from 'rxjs/operators';
|
||||||
|
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';
|
||||||
|
import { followLink } from '../../../../../shared/utils/follow-link-config.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;
|
||||||
|
|
||||||
|
groups: Observable<RemoteData<PaginatedList<Group>>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of subscriptions
|
||||||
|
*/
|
||||||
|
subs: Subscription[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pagination config used to display the list of subgroups
|
||||||
|
*/
|
||||||
|
config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
|
||||||
|
id: 'subgroups-list-pagination',
|
||||||
|
pageSize: 5,
|
||||||
|
currentPage: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
// The search form
|
||||||
|
searchForm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not user has done a search yet
|
||||||
|
*/
|
||||||
|
searchDone: boolean;
|
||||||
|
|
||||||
|
constructor(public groupDataService: GroupDataService,
|
||||||
|
private translateService: TranslateService,
|
||||||
|
private notificationsService: NotificationsService,
|
||||||
|
private formBuilder: FormBuilder) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.subs.push(this.groupDataService.getActiveGroup().subscribe((group: Group) => {
|
||||||
|
if (group != null) {
|
||||||
|
this.groups = this.groupDataService.findAllByHref(group._links.groups.href, {
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: this.config.pageSize
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
this.searchForm = this.formBuilder.group(({
|
||||||
|
query: '',
|
||||||
|
}));
|
||||||
|
this.searchDone = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event triggered when the user changes page
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
onPageChange(event) {
|
||||||
|
this.updateSubgroups({
|
||||||
|
currentPage: event,
|
||||||
|
elementsPerPage: this.config.pageSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the list of subgroups by fetching it from the rest api or cache
|
||||||
|
*/
|
||||||
|
private updateSubgroups(options) {
|
||||||
|
this.groups = this.groupDataService.getGroups(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
isSubgroupOfGroup(possibleSubgroup: Group): Observable<boolean> {
|
||||||
|
return this.groupDataService.getActiveGroup().pipe(take(1),
|
||||||
|
mergeMap((group: Group) => {
|
||||||
|
if (group != null) {
|
||||||
|
return this.groupDataService.findAllByHref(group._links.groups.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);
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteSubgroupFromGroup(group: Group) {
|
||||||
|
// TODO
|
||||||
|
console.log('deleteSubgroup TODO', group);
|
||||||
|
// this.forceUpdateGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
addSubgroupToGroup(group: Group) {
|
||||||
|
// TODO
|
||||||
|
console.log('addSubgroup TODO', group);
|
||||||
|
// this.forceUpdateGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search in the groups (searches by group name and by uuid exact match)
|
||||||
|
* @param data Contains query param
|
||||||
|
*/
|
||||||
|
search(data: any) {
|
||||||
|
this.searchDone = true;
|
||||||
|
const query: string = data.query;
|
||||||
|
this.groups = this.groupDataService.searchGroups(query.trim(), {
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: this.config.pageSize
|
||||||
|
}, followLink('epersons'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force-update the list of groups by first clearing the cache related to groups, then performing a new REST call
|
||||||
|
*/
|
||||||
|
public forceUpdateGroup() {
|
||||||
|
this.groupDataService.clearGroupsRequests();
|
||||||
|
this.search({ query: '' })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* unsub all subscriptions
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||||
|
}
|
||||||
|
}
|
@@ -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,45 @@
|
|||||||
|
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;
|
||||||
|
selectedGroup: Group[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initial state.
|
||||||
|
*/
|
||||||
|
const initialState: GroupRegistryState = {
|
||||||
|
editGroup: null,
|
||||||
|
selectedGroup: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,81 @@
|
|||||||
|
<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}}</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}}</td>
|
||||||
|
<!-- <td>{{getOptionalComColFromName(group.name)}}</td>-->
|
||||||
|
<td>
|
||||||
|
<div class="btn-group edit-field">
|
||||||
|
<button [routerLink]="[group.id]" class="btn btn-outline-primary btn-sm"
|
||||||
|
title="{{messagePrefix + 'table.edit.buttons.edit' | translate}}">
|
||||||
|
<i class="fas fa-edit fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
<button (click)="deleteGroup(group)"
|
||||||
|
class="btn btn-outline-danger btn-sm"
|
||||||
|
title="{{messagePrefix + 'table.edit.buttons.remove' | translate}}">
|
||||||
|
<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,135 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { FormBuilder } from '@angular/forms';
|
||||||
|
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 { hasValue } from '../../../shared/empty.util';
|
||||||
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
|
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||||
|
import { followLink } from '../../../shared/utils/follow-link-config.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 {
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
constructor(private groupService: GroupDataService,
|
||||||
|
private ePersonDataService: EPersonDataService,
|
||||||
|
private translateService: TranslateService,
|
||||||
|
private notificationsService: NotificationsService,
|
||||||
|
private formBuilder: FormBuilder) {
|
||||||
|
this.updateGroups({
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: this.config.pageSize
|
||||||
|
});
|
||||||
|
this.searchForm = this.formBuilder.group(({
|
||||||
|
query: '',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event triggered when the user changes page
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
onPageChange(event) {
|
||||||
|
this.updateGroups({
|
||||||
|
currentPage: event,
|
||||||
|
elementsPerPage: this.config.pageSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the list of groups by fetching it from the rest api or cache
|
||||||
|
*/
|
||||||
|
private updateGroups(options) {
|
||||||
|
this.groups = this.groupService.getGroups(options, followLink('epersons'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
this.groups = this.groupService.searchGroups(query.trim(), {
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: this.config.pageSize
|
||||||
|
}, followLink('epersons'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete Group
|
||||||
|
*/
|
||||||
|
deleteGroup(group: Group) {
|
||||||
|
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: '' })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the amount of members (epersons embedded value of a group) //TODO
|
||||||
|
* @param group
|
||||||
|
*/
|
||||||
|
getMembers(group: Group): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
|
return this.ePersonDataService.findAllByHref(group._links.epersons.href);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
let optionalComColName = '';
|
||||||
|
const uuidMatches = groupName.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) {
|
||||||
|
optionalComColName = uuidMatches[0];
|
||||||
|
}
|
||||||
|
return optionalComColName;
|
||||||
|
}
|
||||||
|
}
|
@@ -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;
|
||||||
|
@@ -1,28 +1,38 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
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 { 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 { FindListOptions, FindListRequest } from '../data/request.models';
|
||||||
|
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { FindListOptions } from '../data/request.models';
|
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
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'
|
||||||
@@ -38,13 +48,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
|
||||||
*
|
*
|
||||||
@@ -59,10 +107,74 @@ export class GroupDataService extends DataService<Group> {
|
|||||||
options.searchParams = [new SearchParam('groupName', groupName)];
|
options.searchParams = [new SearchParam('groupName', groupName)];
|
||||||
|
|
||||||
return this.searchBy(searchHref, options).pipe(
|
return this.searchBy(searchHref, options).pipe(
|
||||||
filter((groups: RemoteData<PaginatedList<Group>>) => !groups.isResponsePending),
|
filter((groups: RemoteData<PaginatedList<Group>>) => !groups.isResponsePending),
|
||||||
take(1),
|
take(1),
|
||||||
map((groups: RemoteData<PaginatedList<Group>>) => groups.payload.totalElements > 0)
|
map((groups: RemoteData<PaginatedList<Group>>) => groups.payload.totalElements > 0)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to delete a group
|
||||||
|
* @param id The group id to delete
|
||||||
|
*/
|
||||||
|
public deleteGroup(group: Group): Observable<boolean> {
|
||||||
|
return this.delete(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create or Update a group
|
||||||
|
* If the group contains an id, it is assumed the eperson already exists and is updated instead
|
||||||
|
* //TODO
|
||||||
|
* @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 {
|
||||||
|
console.log('group create', group)
|
||||||
|
return this.create(group, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* // TODO
|
||||||
|
* @param {DSpaceObject} ePerson The given object
|
||||||
|
*/
|
||||||
|
updateGroup(group: Group): Observable<RemoteData<Group>> {
|
||||||
|
// TODO
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 and returns its REST url
|
||||||
|
*/
|
||||||
|
public clearGroupsRequests(): void {
|
||||||
|
this.getBrowseEndpoint().pipe(take(1)).subscribe((link: string) => {
|
||||||
|
this.requestService.removeByHrefSubstring(link);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
*/
|
*/
|
||||||
@@ -32,6 +40,7 @@ export class Group extends DSpaceObject {
|
|||||||
_links: {
|
_links: {
|
||||||
self: HALLink;
|
self: HALLink;
|
||||||
groups: HALLink;
|
groups: HALLink;
|
||||||
|
epersons: HALLink;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,4 +50,11 @@ export class Group extends DSpaceObject {
|
|||||||
@link(GROUP, true)
|
@link(GROUP, true)
|
||||||
public groups?: Observable<RemoteData<PaginatedList<Group>>>;
|
public groups?: 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>>>;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user