mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-08 10:34:15 +00:00
69110: EPeople admin page - CRUD & search on name, email, md
This commit is contained in:
@@ -170,6 +170,66 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"admin.access-control.epeople.title": "DSpace Angular :: EPeople",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.head": "EPeople",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.search.head": "Search",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.search.scope.name": "Name",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.search.scope.email": "E-mail",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.search.scope.metadata": "Metadata",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.search.button": "Search",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.button.add": "Add EPerson",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.table.id": "ID",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.table.name": "Name",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.table.email": "E-mail",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.table.edit": "Edit",
|
||||||
|
|
||||||
|
"item.access-control.epeople.table.edit.buttons.edit": "Edit",
|
||||||
|
|
||||||
|
"item.access-control.epeople.table.edit.buttons.remove": "Remove",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.no-items": "No EPeople to show.",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.form.create": "Create EPerson",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.form.edit": "Edit EPerson",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.form.firstName": "First name",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.form.lastName": "Last name",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.form.email": "E-mail",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.form.emailHint": "Must be valid e-mail address",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.form.canLogIn": "Can log in",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.form.requireCertificate": "Requires certificate",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.form.notification.created.success": "Successfully created EPerson \"{{name}}\"",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.form.notification.created.failure": "Failed to create EPerson \"{{name}}\"",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.form.notification.edited.success": "Successfully edited EPerson \"{{name}}\"",
|
||||||
|
|
||||||
|
"admin.access-control.epeople.form.notification.edited.failure": "Failed to edit 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.search.breadcrumbs": "Administrative Search",
|
"admin.search.breadcrumbs": "Administrative Search",
|
||||||
|
|
||||||
"admin.search.collection.edit": "Edit",
|
"admin.search.collection.edit": "Edit",
|
||||||
@@ -1500,7 +1560,7 @@
|
|||||||
"relationships.isSingleVolumeOf": "Journal Volume",
|
"relationships.isSingleVolumeOf": "Journal Volume",
|
||||||
|
|
||||||
"relationships.isVolumeOf": "Journal Volumes",
|
"relationships.isVolumeOf": "Journal Volumes",
|
||||||
|
|
||||||
"relationships.isContributorOf": "Contributors",
|
"relationships.isContributorOf": "Contributors",
|
||||||
|
|
||||||
|
|
||||||
|
@@ -0,0 +1,14 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { EPeopleRegistryComponent } from './epeople-registry/epeople-registry.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild([
|
||||||
|
{ path: 'epeople', component: EPeopleRegistryComponent, data: { title: 'admin.access-control.epeople.title' } },
|
||||||
|
])
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AdminAccessControlRoutingModule {
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,26 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
import { AdminAccessControlRoutingModule } from './admin-access-control-routing.module';
|
||||||
|
import { EPeopleRegistryComponent } from './epeople-registry/epeople-registry.component';
|
||||||
|
import { EPersonFormComponent } from './epeople-registry/eperson-form/eperson-form.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
SharedModule,
|
||||||
|
RouterModule,
|
||||||
|
TranslateModule,
|
||||||
|
AdminAccessControlRoutingModule
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
EPeopleRegistryComponent,
|
||||||
|
EPersonFormComponent
|
||||||
|
],
|
||||||
|
entryComponents: []
|
||||||
|
})
|
||||||
|
export class AdminAccessControlModule {
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,49 @@
|
|||||||
|
import { Action } from '@ngrx/store';
|
||||||
|
import { EPerson } from '../../../core/eperson/models/eperson.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 EPeopleRegistryActionTypes = {
|
||||||
|
|
||||||
|
EDIT_EPERSON: type('dspace/epeople-registry/EDIT_EPERSON'),
|
||||||
|
CANCEL_EDIT_EPERSON: type('dspace/epeople-registry/CANCEL_SCHEMA'),
|
||||||
|
};
|
||||||
|
|
||||||
|
/* tslint:disable:max-classes-per-file */
|
||||||
|
/**
|
||||||
|
* Used to edit an EPerson in the EPeople registry
|
||||||
|
*/
|
||||||
|
export class EPeopleRegistryEditEPersonAction implements Action {
|
||||||
|
type = EPeopleRegistryActionTypes.EDIT_EPERSON;
|
||||||
|
|
||||||
|
eperson: EPerson;
|
||||||
|
|
||||||
|
constructor(registry: EPerson) {
|
||||||
|
this.eperson = registry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to cancel the editing of an EPerson in the EPeople registry
|
||||||
|
*/
|
||||||
|
export class EPeopleRegistryCancelEPersonAction implements Action {
|
||||||
|
type = EPeopleRegistryActionTypes.CANCEL_EDIT_EPERSON;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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 EPeopleRegistryAction
|
||||||
|
= EPeopleRegistryEditEPersonAction
|
||||||
|
| EPeopleRegistryCancelEPersonAction
|
@@ -0,0 +1,90 @@
|
|||||||
|
<div class="container">
|
||||||
|
<div class="epeople-registry row">
|
||||||
|
<div class="col-12">
|
||||||
|
|
||||||
|
<h2 id="header" class="border-bottom pb-2">{{labelPrefix + 'head' | translate}}</h2>
|
||||||
|
|
||||||
|
<ds-eperson-form *ngIf="isEPersonFormShown" (submitForm)="forceUpdateEPeople()"
|
||||||
|
(cancelForm)="isEPersonFormShown = false"></ds-eperson-form>
|
||||||
|
|
||||||
|
<div class="button-row top d-flex">
|
||||||
|
<button class="mr-auto btn btn-success"
|
||||||
|
(click)="isEPersonFormShown = true">
|
||||||
|
<i class="fas fa-plus"></i>
|
||||||
|
<span class="d-none d-sm-inline"> {{labelPrefix + 'button.add' | translate}}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 id="search" class="border-bottom pb-2">{{labelPrefix + 'search.head' | translate}}</h3>
|
||||||
|
<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="name">{{labelPrefix + 'search.scope.name' | translate}}</option>
|
||||||
|
<option value="email">{{labelPrefix + 'search.scope.email' | translate}}</option>
|
||||||
|
<option value="metadata">{{labelPrefix + 'search.scope.metadata' | 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">{{ labelPrefix + '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="epeople" class="table table-striped table-hover table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">{{labelPrefix + 'table.id' | translate}}</th>
|
||||||
|
<th scope="col">{{labelPrefix + 'table.name' | translate}}</th>
|
||||||
|
<th scope="col">{{labelPrefix + 'table.email' | translate}}</th>
|
||||||
|
<th>{{labelPrefix + 'table.edit' | translate}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let eperson of (ePeople | async)?.payload?.page"
|
||||||
|
[ngClass]="{'table-primary' : isActive(eperson) | async}">
|
||||||
|
<td>{{eperson.id}}</td>
|
||||||
|
<td>{{eperson.name}}</td>
|
||||||
|
<td>{{eperson.email}}</td>
|
||||||
|
<td>
|
||||||
|
<div class="btn-group edit-field">
|
||||||
|
<button (click)="editEPerson(eperson)" class="btn btn-outline-primary btn-sm"
|
||||||
|
title="{{labelPrefix + 'table.edit.buttons.edit' | translate}}">
|
||||||
|
<i class="fas fa-edit fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
<button (click)="deleteEPerson(eperson)"
|
||||||
|
class="btn btn-outline-danger btn-sm"
|
||||||
|
title="{{labelPrefix + '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="(ePeople | async)?.payload?.totalElements == 0" class="alert alert-info w-100 mb-2" role="alert">
|
||||||
|
{{labelPrefix + 'no-items' | translate}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,190 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { FormBuilder } from '@angular/forms';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { map, 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 { EPerson } from '../../../core/eperson/models/eperson.model';
|
||||||
|
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-epeople-registry',
|
||||||
|
templateUrl: './epeople-registry.component.html',
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* A component used for managing all existing epeople within the repository.
|
||||||
|
* The admin can create, edit or delete epeople here.
|
||||||
|
*/
|
||||||
|
export class EPeopleRegistryComponent {
|
||||||
|
|
||||||
|
labelPrefix = 'admin.access-control.epeople.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of all the current EPeople within the repository or the result of the search
|
||||||
|
*/
|
||||||
|
ePeople: Observable<RemoteData<PaginatedList<EPerson>>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pagination config used to display the list of epeople
|
||||||
|
*/
|
||||||
|
config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
|
||||||
|
id: 'epeople-list-pagination',
|
||||||
|
pageSize: 5,
|
||||||
|
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: 'name',
|
||||||
|
query: '',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event triggered when the user changes page
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
onPageChange(event) {
|
||||||
|
this.updateEPeople({
|
||||||
|
currentPage: event,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.updateEPeople({
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: this.config.pageSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search in the EPeople by name, email or metadata
|
||||||
|
* @param data Contains scope and query param
|
||||||
|
*/
|
||||||
|
search(data: any) {
|
||||||
|
const query = data.query;
|
||||||
|
const scope = data.scope;
|
||||||
|
switch (scope) {
|
||||||
|
case 'name':
|
||||||
|
this.ePeople = this.epersonService.getEpeopleByName(query, {
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: this.config.pageSize
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'email':
|
||||||
|
this.ePeople = this.epersonService.getEpeopleByEmail(query, {
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: this.config.pageSize
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'metadata':
|
||||||
|
this.ePeople = this.epersonService.getEpeopleByMetadata(query, {
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: this.config.pageSize
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.ePeople = this.epersonService.getEpeopleByEmail(query, {
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: this.config.pageSize
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 metadata schema
|
||||||
|
* @param schema
|
||||||
|
*/
|
||||||
|
editEPerson(ePerson: EPerson) {
|
||||||
|
this.getActiveEPerson().pipe(take(1)).subscribe((activeEPerson) => {
|
||||||
|
if (ePerson === activeEPerson) {
|
||||||
|
this.epersonService.cancelEditEPerson();
|
||||||
|
this.isEPersonFormShown = false;
|
||||||
|
} else {
|
||||||
|
this.epersonService.editEPerson(ePerson);
|
||||||
|
this.isEPersonFormShown = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.scrollToTop()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete EPerson
|
||||||
|
*/
|
||||||
|
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.success(this.translateService.get(this.labelPrefix + 'notification.deleted.failure', { name: ePerson.name }));
|
||||||
|
}
|
||||||
|
this.epersonService.cancelEditEPerson();
|
||||||
|
this.isEPersonFormShown = false;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollToTop() {
|
||||||
|
(function smoothscroll() {
|
||||||
|
const currentScroll = document.documentElement.scrollTop || document.body.scrollTop;
|
||||||
|
if (currentScroll > 0) {
|
||||||
|
window.requestAnimationFrame(smoothscroll);
|
||||||
|
window.scrollTo(0, currentScroll - (currentScroll / 8));
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,49 @@
|
|||||||
|
import { EPerson } from '../../../core/eperson/models/eperson.model';
|
||||||
|
import {
|
||||||
|
EPeopleRegistryAction,
|
||||||
|
EPeopleRegistryActionTypes,
|
||||||
|
EPeopleRegistryEditEPersonAction
|
||||||
|
} from './epeople-registry.actions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The metadata registry state.
|
||||||
|
* @interface EPeopleRegistryState
|
||||||
|
*/
|
||||||
|
export interface EPeopleRegistryState {
|
||||||
|
editEPerson: EPerson;
|
||||||
|
selectedEPeople: EPerson[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The initial state.
|
||||||
|
*/
|
||||||
|
const initialState: EPeopleRegistryState = {
|
||||||
|
editEPerson: null,
|
||||||
|
selectedEPeople: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reducer that handles EPeopleRegistryActions to modify EPeople
|
||||||
|
* @param state The current EPeopleRegistryState
|
||||||
|
* @param action The EPeopleRegistryAction to perform on the state
|
||||||
|
*/
|
||||||
|
export function ePeopleRegistryReducer(state = initialState, action: EPeopleRegistryAction): EPeopleRegistryState {
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
|
||||||
|
case EPeopleRegistryActionTypes.EDIT_EPERSON: {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
editEPerson: (action as EPeopleRegistryEditEPersonAction).eperson
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
case EPeopleRegistryActionTypes.CANCEL_EDIT_EPERSON: {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
editEPerson: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,17 @@
|
|||||||
|
<div *ngIf="epersonService.getActiveEPerson() | async; then editheader; else createHeader"></div>
|
||||||
|
|
||||||
|
<ng-template #createHeader>
|
||||||
|
<h4>{{messagePrefix + '.create' | translate}}</h4>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #editheader>
|
||||||
|
<h4>{{messagePrefix + '.edit' | translate}}</h4>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ds-form [formId]="formId"
|
||||||
|
[formModel]="formModel"
|
||||||
|
[formGroup]="formGroup"
|
||||||
|
[formLayout]="formLayout"
|
||||||
|
(cancel)="onCancel()"
|
||||||
|
(submitForm)="onSubmit()">
|
||||||
|
</ds-form>
|
@@ -0,0 +1,291 @@
|
|||||||
|
import { Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
|
||||||
|
import { FormGroup } from '@angular/forms';
|
||||||
|
import {
|
||||||
|
DynamicCheckboxModel,
|
||||||
|
DynamicFormControlModel,
|
||||||
|
DynamicFormLayout,
|
||||||
|
DynamicInputModel
|
||||||
|
} from '@ng-dynamic-forms/core';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { combineLatest } from 'rxjs/internal/observable/combineLatest';
|
||||||
|
import { take } from 'rxjs/operators';
|
||||||
|
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
|
||||||
|
import { EPerson } from '../../../../core/eperson/models/eperson.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-eperson-form',
|
||||||
|
templateUrl: './eperson-form.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* A form used for creating and editing EPeople
|
||||||
|
*/
|
||||||
|
export class EPersonFormComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
labelPrefix = 'admin.access-control.epeople.form.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A unique id used for ds-form
|
||||||
|
*/
|
||||||
|
formId = 'eperson-form';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The labelPrefix for all messages related to this form
|
||||||
|
*/
|
||||||
|
messagePrefix = 'admin.access-control.epeople.form';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamic input models for the inputs of form
|
||||||
|
*/
|
||||||
|
firstName: DynamicInputModel;
|
||||||
|
lastName: DynamicInputModel;
|
||||||
|
email: DynamicInputModel;
|
||||||
|
// booleans
|
||||||
|
canLogIn: DynamicCheckboxModel;
|
||||||
|
requireCertificate: DynamicCheckboxModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of all dynamic input models
|
||||||
|
*/
|
||||||
|
formModel: DynamicFormControlModel[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layout used for structuring the form inputs
|
||||||
|
*/
|
||||||
|
formLayout: DynamicFormLayout = {
|
||||||
|
firstName: {
|
||||||
|
grid: {
|
||||||
|
host: 'row'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lastName: {
|
||||||
|
grid: {
|
||||||
|
host: 'row'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
grid: {
|
||||||
|
host: 'row'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
canLogIn: {
|
||||||
|
grid: {
|
||||||
|
host: 'col col-sm-6 d-inline-block'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
requireCertificate: {
|
||||||
|
grid: {
|
||||||
|
host: 'col col-sm-6 d-inline-block'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
|
||||||
|
constructor(private epersonService: EPersonDataService,
|
||||||
|
private formBuilderService: FormBuilderService,
|
||||||
|
private translateService: TranslateService,
|
||||||
|
private notificationsService: NotificationsService,) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
combineLatest(
|
||||||
|
this.translateService.get(`${this.messagePrefix}.firstName`),
|
||||||
|
this.translateService.get(`${this.messagePrefix}.lastName`),
|
||||||
|
this.translateService.get(`${this.messagePrefix}.email`),
|
||||||
|
this.translateService.get(`${this.messagePrefix}.canLogIn`),
|
||||||
|
this.translateService.get(`${this.messagePrefix}.requireCertificate`),
|
||||||
|
this.translateService.get(`${this.messagePrefix}.emailHint`),
|
||||||
|
).subscribe(([firstName, lastName, email, canLogIn, requireCertificate, emailHint]) => {
|
||||||
|
this.firstName = new DynamicInputModel({
|
||||||
|
id: 'firstName',
|
||||||
|
label: firstName,
|
||||||
|
name: 'firstName',
|
||||||
|
validators: {
|
||||||
|
required: null,
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
this.lastName = new DynamicInputModel({
|
||||||
|
id: 'lastName',
|
||||||
|
label: lastName,
|
||||||
|
name: 'lastName',
|
||||||
|
validators: {
|
||||||
|
required: null,
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
this.email = new DynamicInputModel({
|
||||||
|
id: 'email',
|
||||||
|
label: email,
|
||||||
|
name: 'email',
|
||||||
|
validators: {
|
||||||
|
required: null,
|
||||||
|
pattern: '^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$'
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
hint: emailHint
|
||||||
|
});
|
||||||
|
this.canLogIn = new DynamicCheckboxModel(
|
||||||
|
{
|
||||||
|
id: 'canLogIn',
|
||||||
|
label: canLogIn,
|
||||||
|
name: 'canLogIn',
|
||||||
|
});
|
||||||
|
this.requireCertificate = new DynamicCheckboxModel(
|
||||||
|
{
|
||||||
|
id: 'requireCertificate',
|
||||||
|
label: requireCertificate,
|
||||||
|
name: 'requireCertificate',
|
||||||
|
});
|
||||||
|
this.formModel = [
|
||||||
|
this.firstName,
|
||||||
|
this.lastName,
|
||||||
|
this.email,
|
||||||
|
this.canLogIn,
|
||||||
|
this.requireCertificate,
|
||||||
|
];
|
||||||
|
this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
|
||||||
|
this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
|
||||||
|
this.formGroup.patchValue({
|
||||||
|
firstName: eperson != null ? eperson.firstMetadataValue('eperson.firstname') : '',
|
||||||
|
lastName: eperson != null ? eperson.firstMetadataValue('eperson.lastname') : '',
|
||||||
|
email: eperson != null ? eperson.email : '',
|
||||||
|
canLogIn: eperson != null ? eperson.canLogIn : true,
|
||||||
|
requireCertificate: eperson != null ? eperson.requireCertificate : false,
|
||||||
|
selfRegistered: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop editing the currently selected eperson
|
||||||
|
*/
|
||||||
|
onCancel() {
|
||||||
|
this.epersonService.cancelEditEPerson();
|
||||||
|
this.cancelForm.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.epersonService.getActiveEPerson().pipe(take(1)).subscribe(
|
||||||
|
(ePerson: EPerson) => {
|
||||||
|
const values = {
|
||||||
|
metadata: {
|
||||||
|
'eperson.firstname': [
|
||||||
|
{
|
||||||
|
value: this.firstName.value
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'eperson.lastname': [
|
||||||
|
{
|
||||||
|
value: this.lastName.value
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
email: this.email.value,
|
||||||
|
canLogIn: this.canLogIn.value,
|
||||||
|
requireCertificate: this.requireCertificate.value,
|
||||||
|
selfRegistered: false,
|
||||||
|
};
|
||||||
|
if (ePerson == null) {
|
||||||
|
this.createNewEPerson(values);
|
||||||
|
} else {
|
||||||
|
this.editEPerson(ePerson, values);
|
||||||
|
}
|
||||||
|
this.clearFields();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new EPerson based on given values from form
|
||||||
|
* @param values
|
||||||
|
*/
|
||||||
|
createNewEPerson(values) {
|
||||||
|
this.epersonService.createOrUpdateEPerson(Object.assign(new EPerson(), values))
|
||||||
|
.pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload())
|
||||||
|
.subscribe((newEPerson: EPerson) => {
|
||||||
|
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.created.success', { name: newEPerson.name }));
|
||||||
|
this.submitForm.emit(newEPerson);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edits existing EPerson based on given values from form and old EPerson
|
||||||
|
* @param ePerson
|
||||||
|
* @param values
|
||||||
|
*/
|
||||||
|
editEPerson(ePerson: EPerson, values) {
|
||||||
|
this.epersonService.createOrUpdateEPerson(Object.assign(new EPerson(), {
|
||||||
|
id: ePerson.id,
|
||||||
|
metadata: {
|
||||||
|
'eperson.firstname': [
|
||||||
|
{
|
||||||
|
value: (this.firstName.value ? this.firstName.value : ePerson.firstMetadataValue('eperson.firstname'))
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'eperson.lastname': [
|
||||||
|
{
|
||||||
|
value: (this.lastName.value ? this.lastName.value : ePerson.firstMetadataValue('eperson.lastname'))
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
email: (hasValue(values.email) ? values.email : ePerson.email),
|
||||||
|
canLogIn: (hasValue(values.canLogIn) ? values.canLogIn : ePerson.canLogIn),
|
||||||
|
requireCertificate: (hasValue(values.requireCertificate) ? values.requireCertificate : ePerson.requireCertificate),
|
||||||
|
selfRegistered: false,
|
||||||
|
_links: ePerson._links,
|
||||||
|
}))
|
||||||
|
.pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload())
|
||||||
|
.subscribe((updatedEPerson: EPerson) => {
|
||||||
|
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.edited.success', { name: updatedEPerson.name }));
|
||||||
|
this.submitForm.emit(updatedEPerson);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset all input-fields to be empty
|
||||||
|
*/
|
||||||
|
clearFields() {
|
||||||
|
this.formGroup.patchValue({
|
||||||
|
firstName: '',
|
||||||
|
lastName: '',
|
||||||
|
email: '',
|
||||||
|
canLogin: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel the current edit when component is destroyed
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.onCancel();
|
||||||
|
}
|
||||||
|
}
|
@@ -1,11 +1,12 @@
|
|||||||
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 { URLCombiner } from '../core/url-combiner/url-combiner';
|
||||||
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';
|
||||||
|
|
||||||
const REGISTRIES_MODULE_PATH = 'registries';
|
const REGISTRIES_MODULE_PATH = 'registries';
|
||||||
|
const ACCESS_CONTROL_MODULE_PATH = 'access-control';
|
||||||
|
|
||||||
export function getRegistriesModulePath() {
|
export function getRegistriesModulePath() {
|
||||||
return new URLCombiner(getAdminModulePath(), REGISTRIES_MODULE_PATH).toString();
|
return new URLCombiner(getAdminModulePath(), REGISTRIES_MODULE_PATH).toString();
|
||||||
@@ -18,6 +19,10 @@ export function getRegistriesModulePath() {
|
|||||||
path: REGISTRIES_MODULE_PATH,
|
path: REGISTRIES_MODULE_PATH,
|
||||||
loadChildren: './admin-registries/admin-registries.module#AdminRegistriesModule'
|
loadChildren: './admin-registries/admin-registries.module#AdminRegistriesModule'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: ACCESS_CONTROL_MODULE_PATH,
|
||||||
|
loadChildren: './admin-access-control/admin-access-control.module#AdminAccessControlModule'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'search',
|
path: 'search',
|
||||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||||
|
@@ -1,23 +1,23 @@
|
|||||||
import { Component, Injector, OnInit } from '@angular/core';
|
import { Component, Injector, OnInit } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
|
||||||
import { slideHorizontal, slideSidebar } from '../../shared/animations/slide';
|
|
||||||
import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service';
|
|
||||||
import { MenuService } from '../../shared/menu/menu.service';
|
|
||||||
import { MenuID, MenuItemType } from '../../shared/menu/initial-menus-state';
|
|
||||||
import { MenuComponent } from '../../shared/menu/menu.component';
|
|
||||||
import { TextMenuItemModel } from '../../shared/menu/menu-item/models/text.model';
|
|
||||||
import { LinkMenuItemModel } from '../../shared/menu/menu-item/models/link.model';
|
|
||||||
import { AuthService } from '../../core/auth/auth.service';
|
|
||||||
import { first, map } from 'rxjs/operators';
|
|
||||||
import { combineLatest as combineLatestObservable } from 'rxjs';
|
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { OnClickMenuItemModel } from '../../shared/menu/menu-item/models/onclick.model';
|
import { combineLatest as combineLatestObservable } from 'rxjs';
|
||||||
import { CreateCommunityParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { first, map } from 'rxjs/operators';
|
||||||
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
|
import { slideHorizontal, slideSidebar } from '../../shared/animations/slide';
|
||||||
import { CreateCollectionParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component';
|
import { CreateCollectionParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component';
|
||||||
import { EditItemSelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component';
|
import { CreateCommunityParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component';
|
||||||
import { EditCommunitySelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component';
|
import { CreateItemParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component';
|
||||||
import { EditCollectionSelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component';
|
import { EditCollectionSelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component';
|
||||||
import {CreateItemParentSelectorComponent} from '../../shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component';
|
import { EditCommunitySelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component';
|
||||||
|
import { EditItemSelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component';
|
||||||
|
import { MenuID, MenuItemType } from '../../shared/menu/initial-menus-state';
|
||||||
|
import { LinkMenuItemModel } from '../../shared/menu/menu-item/models/link.model';
|
||||||
|
import { OnClickMenuItemModel } from '../../shared/menu/menu-item/models/onclick.model';
|
||||||
|
import { TextMenuItemModel } from '../../shared/menu/menu-item/models/text.model';
|
||||||
|
import { MenuComponent } from '../../shared/menu/menu.component';
|
||||||
|
import { MenuService } from '../../shared/menu/menu.service';
|
||||||
|
import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component representing the admin sidebar
|
* Component representing the admin sidebar
|
||||||
@@ -325,7 +325,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.LINK,
|
||||||
text: 'menu.section.access_control_people',
|
text: 'menu.section.access_control_people',
|
||||||
link: ''
|
link: '/admin/access-control/epeople'
|
||||||
} as LinkMenuItemModel,
|
} as LinkMenuItemModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { AdminRegistriesModule } from './admin-registries/admin-registries.module';
|
|
||||||
import { AdminRoutingModule } from './admin-routing.module';
|
|
||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
|
import { AdminAccessControlModule } from './admin-access-control/admin-access-control.module';
|
||||||
|
import { AdminRegistriesModule } from './admin-registries/admin-registries.module';
|
||||||
import { AdminSearchPageComponent } from './admin-search-page/admin-search-page.component';
|
import { AdminSearchPageComponent } from './admin-search-page/admin-search-page.component';
|
||||||
import { SearchPageModule } from '../+search-page/search-page.module';
|
import { SearchPageModule } from '../+search-page/search-page.module';
|
||||||
import { ItemAdminSearchResultListElementComponent } from './admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component';
|
import { ItemAdminSearchResultListElementComponent } from './admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component';
|
||||||
@@ -15,7 +15,7 @@ import { ItemAdminSearchResultActionsComponent } from './admin-search-page/admin
|
|||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
AdminRegistriesModule,
|
AdminRegistriesModule,
|
||||||
AdminRoutingModule,
|
AdminAccessControlModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
SearchPageModule
|
SearchPageModule
|
||||||
],
|
],
|
||||||
|
@@ -1,30 +1,34 @@
|
|||||||
import { ActionReducerMap, createSelector, MemoizedSelector } from '@ngrx/store';
|
|
||||||
import * as fromRouter from '@ngrx/router-store';
|
import * as fromRouter from '@ngrx/router-store';
|
||||||
|
import { ActionReducerMap, createSelector, MemoizedSelector } from '@ngrx/store';
|
||||||
import { hostWindowReducer, HostWindowState } from './shared/search/host-window.reducer';
|
import {
|
||||||
import { CommunityListReducer, CommunityListState } from './community-list-page/community-list.reducer';
|
ePeopleRegistryReducer,
|
||||||
import { formReducer, FormState } from './shared/form/form.reducer';
|
EPeopleRegistryState
|
||||||
import { sidebarReducer, SidebarState } from './shared/sidebar/sidebar.reducer';
|
} from './+admin/admin-access-control/epeople-registry/epeople-registry.reducers';
|
||||||
import { sidebarFilterReducer, SidebarFiltersState } from './shared/sidebar/filter/sidebar-filter.reducer';
|
|
||||||
import { filterReducer, SearchFiltersState } from './shared/search/search-filters/search-filter/search-filter.reducer';
|
|
||||||
import { notificationsReducer, NotificationsState } from './shared/notifications/notifications.reducers';
|
|
||||||
import { truncatableReducer, TruncatablesState } from './shared/truncatable/truncatable.reducer';
|
|
||||||
import {
|
import {
|
||||||
metadataRegistryReducer,
|
metadataRegistryReducer,
|
||||||
MetadataRegistryState
|
MetadataRegistryState
|
||||||
} from './+admin/admin-registries/metadata-registry/metadata-registry.reducers';
|
} from './+admin/admin-registries/metadata-registry/metadata-registry.reducers';
|
||||||
|
import { CommunityListReducer, CommunityListState } from './community-list-page/community-list.reducer';
|
||||||
import { hasValue } from './shared/empty.util';
|
import { hasValue } from './shared/empty.util';
|
||||||
import { cssVariablesReducer, CSSVariablesState } from './shared/sass-helper/sass-helper.reducer';
|
import {
|
||||||
|
NameVariantListsState,
|
||||||
|
nameVariantReducer
|
||||||
|
} from './shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.reducer';
|
||||||
|
import { formReducer, FormState } from './shared/form/form.reducer';
|
||||||
import { menusReducer, MenusState } from './shared/menu/menu.reducer';
|
import { menusReducer, MenusState } from './shared/menu/menu.reducer';
|
||||||
|
import { notificationsReducer, NotificationsState } from './shared/notifications/notifications.reducers';
|
||||||
import {
|
import {
|
||||||
selectableListReducer,
|
selectableListReducer,
|
||||||
SelectableListsState
|
SelectableListsState
|
||||||
} from './shared/object-list/selectable-list/selectable-list.reducer';
|
} from './shared/object-list/selectable-list/selectable-list.reducer';
|
||||||
import { ObjectSelectionListState, objectSelectionReducer } from './shared/object-select/object-select.reducer';
|
import { ObjectSelectionListState, objectSelectionReducer } from './shared/object-select/object-select.reducer';
|
||||||
import {
|
import { cssVariablesReducer, CSSVariablesState } from './shared/sass-helper/sass-helper.reducer';
|
||||||
NameVariantListsState,
|
|
||||||
nameVariantReducer
|
import { hostWindowReducer, HostWindowState } from './shared/search/host-window.reducer';
|
||||||
} from './shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.reducer';
|
import { filterReducer, SearchFiltersState } from './shared/search/search-filters/search-filter/search-filter.reducer';
|
||||||
|
import { sidebarFilterReducer, SidebarFiltersState } from './shared/sidebar/filter/sidebar-filter.reducer';
|
||||||
|
import { sidebarReducer, SidebarState } from './shared/sidebar/sidebar.reducer';
|
||||||
|
import { truncatableReducer, TruncatablesState } from './shared/truncatable/truncatable.reducer';
|
||||||
|
|
||||||
export interface AppState {
|
export interface AppState {
|
||||||
router: fromRouter.RouterReducerState;
|
router: fromRouter.RouterReducerState;
|
||||||
@@ -42,6 +46,7 @@ export interface AppState {
|
|||||||
selectableLists: SelectableListsState;
|
selectableLists: SelectableListsState;
|
||||||
relationshipLists: NameVariantListsState;
|
relationshipLists: NameVariantListsState;
|
||||||
communityList: CommunityListState;
|
communityList: CommunityListState;
|
||||||
|
epeopleRegistry: EPeopleRegistryState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const appReducers: ActionReducerMap<AppState> = {
|
export const appReducers: ActionReducerMap<AppState> = {
|
||||||
@@ -60,6 +65,7 @@ export const appReducers: ActionReducerMap<AppState> = {
|
|||||||
selectableLists: selectableListReducer,
|
selectableLists: selectableListReducer,
|
||||||
relationshipLists: nameVariantReducer,
|
relationshipLists: nameVariantReducer,
|
||||||
communityList: CommunityListReducer,
|
communityList: CommunityListReducer,
|
||||||
|
epeopleRegistry: ePeopleRegistryReducer,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const routerStateSelector = (state: AppState) => state.router;
|
export const routerStateSelector = (state: AppState) => state.router;
|
||||||
|
6
src/app/core/cache/object-cache.service.ts
vendored
6
src/app/core/cache/object-cache.service.ts
vendored
@@ -10,6 +10,7 @@ import { coreSelector } from '../core.selectors';
|
|||||||
import { RestRequestMethod } from '../data/rest-request-method';
|
import { RestRequestMethod } from '../data/rest-request-method';
|
||||||
import { selfLinkFromUuidSelector } from '../index/index.selectors';
|
import { selfLinkFromUuidSelector } from '../index/index.selectors';
|
||||||
import { GenericConstructor } from '../shared/generic-constructor';
|
import { GenericConstructor } from '../shared/generic-constructor';
|
||||||
|
import { getClassForType } from './builders/build-decorators';
|
||||||
import { LinkService } from './builders/link.service';
|
import { LinkService } from './builders/link.service';
|
||||||
import {
|
import {
|
||||||
AddPatchObjectCacheAction,
|
AddPatchObjectCacheAction,
|
||||||
@@ -20,7 +21,6 @@ import {
|
|||||||
|
|
||||||
import { CacheableObject, ObjectCacheEntry, ObjectCacheState } from './object-cache.reducer';
|
import { CacheableObject, ObjectCacheEntry, ObjectCacheState } from './object-cache.reducer';
|
||||||
import { AddToSSBAction } from './server-sync-buffer.actions';
|
import { AddToSSBAction } from './server-sync-buffer.actions';
|
||||||
import { getClassForType } from './builders/build-decorators';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base selector function to select the object cache in the store
|
* The base selector function to select the object cache in the store
|
||||||
@@ -48,7 +48,7 @@ export class ObjectCacheService {
|
|||||||
constructor(
|
constructor(
|
||||||
private store: Store<CoreState>,
|
private store: Store<CoreState>,
|
||||||
private linkService: LinkService
|
private linkService: LinkService
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -276,6 +276,8 @@ export class ObjectCacheService {
|
|||||||
* list of operations to perform
|
* list of operations to perform
|
||||||
*/
|
*/
|
||||||
public addPatch(selfLink: string, patch: Operation[]) {
|
public addPatch(selfLink: string, patch: Operation[]) {
|
||||||
|
console.log('selfLink addPatch', selfLink)
|
||||||
|
console.log('patch addPatch', patch)
|
||||||
this.store.dispatch(new AddPatchObjectCacheAction(selfLink, patch));
|
this.store.dispatch(new AddPatchObjectCacheAction(selfLink, patch));
|
||||||
this.store.dispatch(new AddToSSBAction(selfLink, RestRequestMethod.PATCH));
|
this.store.dispatch(new AddToSSBAction(selfLink, RestRequestMethod.PATCH));
|
||||||
}
|
}
|
||||||
|
@@ -1,31 +1,52 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Store } from '@ngrx/store';
|
import { createSelector, select, Store } from '@ngrx/store';
|
||||||
|
import { Operation } from 'fast-json-patch/lib/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { filter, mergeMap, take } from 'rxjs/operators';
|
||||||
|
import {
|
||||||
|
EPeopleRegistryCancelEPersonAction,
|
||||||
|
EPeopleRegistryEditEPersonAction
|
||||||
|
} from '../../+admin/admin-access-control/epeople-registry/epeople-registry.actions';
|
||||||
|
import { EPeopleRegistryState } from '../../+admin/admin-access-control/epeople-registry/epeople-registry.reducers';
|
||||||
|
import { AppState } from '../../app.reducer';
|
||||||
|
import { hasValue } from '../../shared/empty.util';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
import { dataService } from '../cache/builders/build-decorators';
|
import { dataService } from '../cache/builders/build-decorators';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
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 { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { CoreState } from '../core.reducers';
|
|
||||||
import { DataService } from '../data/data.service';
|
import { DataService } from '../data/data.service';
|
||||||
import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
|
import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
|
||||||
|
import { PaginatedList } from '../data/paginated-list';
|
||||||
|
import { RemoteData } from '../data/remote-data';
|
||||||
|
import { FindListOptions, FindListRequest, PatchRequest, } from '../data/request.models';
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
|
import { getRemoteDataPayload, getSucceededRemoteData } from '../shared/operators';
|
||||||
import { EPerson } from './models/eperson.model';
|
import { EPerson } from './models/eperson.model';
|
||||||
import { EPERSON } from './models/eperson.resource-type';
|
import { EPERSON } from './models/eperson.resource-type';
|
||||||
|
|
||||||
|
const ePeopleRegistryStateSelector = (state: AppState) => state.epeopleRegistry;
|
||||||
|
const editEPersonSelector = createSelector(ePeopleRegistryStateSelector, (ePeopleRegistryState: EPeopleRegistryState) => ePeopleRegistryState.editEPerson);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A service to retrieve {@link EPerson}s from the REST API
|
* A service to retrieve {@link EPerson}s from the REST API & EPerson related CRUD actions
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@dataService(EPERSON)
|
@dataService(EPERSON)
|
||||||
export class EPersonDataService extends DataService<EPerson> {
|
export class EPersonDataService extends DataService<EPerson> {
|
||||||
|
|
||||||
protected linkPath: 'epersons';
|
protected linkPath = 'epersons';
|
||||||
|
protected searchByNamePath = 'byName';
|
||||||
|
protected searchByEmailPath = 'byEmail';
|
||||||
|
protected searchByMetadataPath = 'byMetadata';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
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,
|
||||||
protected notificationsService: NotificationsService,
|
protected notificationsService: NotificationsService,
|
||||||
@@ -35,4 +56,177 @@ export class EPersonDataService extends DataService<EPerson> {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves all EPeople
|
||||||
|
* @param pagination The pagination info used to retrieve the EPeople
|
||||||
|
*/
|
||||||
|
public getEPeople(options: FindListOptions = {}): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
|
const hrefObs = this.getFindAllHref(options, this.linkPath);
|
||||||
|
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<EPerson>(hrefObs) as Observable<RemoteData<PaginatedList<EPerson>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a search result list of EPeople, by name query (/eperson/epersons/search/{@link searchByNamePath}?q=<>)
|
||||||
|
* @param query name query
|
||||||
|
* @param options
|
||||||
|
* @param linksToFollow
|
||||||
|
*/
|
||||||
|
public getEpeopleByName(query: string, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<EPerson>>): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
|
const searchParams = [new SearchParam('q', query)];
|
||||||
|
return this.getEPeopleBy(searchParams, this.searchByNamePath, options, ...linksToFollow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a search result list of EPeople, by email query (/eperson/epersons/search/{@link searchByEmailPath}?email=<>)
|
||||||
|
* @param query email query
|
||||||
|
* @param options
|
||||||
|
* @param linksToFollow
|
||||||
|
*/
|
||||||
|
public getEpeopleByEmail(query: string, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<EPerson>>): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
|
const searchParams = [new SearchParam('email', query)];
|
||||||
|
return this.getEPeopleBy(searchParams, this.searchByEmailPath, options, ...linksToFollow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a search result list of EPeople, by metadata query (/eperson/epersons/search/{@link searchByMetadataPath}?query=<>)
|
||||||
|
* @param query metadata query
|
||||||
|
* @param options
|
||||||
|
* @param linksToFollow
|
||||||
|
*/
|
||||||
|
public getEpeopleByMetadata(query: string, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<EPerson>>): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
|
const searchParams = [new SearchParam('query', query)];
|
||||||
|
return this.getEPeopleBy(searchParams, this.searchByMetadataPath, options, ...linksToFollow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a search result list of EPeople in a given searchMethod, with given searchParams
|
||||||
|
* @param searchParams query parameters in the search
|
||||||
|
* @param searchMethod searchBy path
|
||||||
|
* @param options
|
||||||
|
* @param linksToFollow
|
||||||
|
*/
|
||||||
|
private getEPeopleBy(searchParams: SearchParam[], searchMethod: string, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<EPerson>>): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
|
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(searchMethod, findListOptions, ...linksToFollow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create or Update an EPerson
|
||||||
|
* If the EPerson contains an id, it is assumed the eperson already exists and is updated instead
|
||||||
|
* @param ePerson The EPerson to create or update
|
||||||
|
*/
|
||||||
|
public createOrUpdateEPerson(ePerson: EPerson): Observable<RemoteData<EPerson>> {
|
||||||
|
const isUpdate = hasValue(ePerson.id);
|
||||||
|
if (isUpdate) {
|
||||||
|
return this.updateEPerson(ePerson);
|
||||||
|
} else {
|
||||||
|
return this.create(ePerson, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new patch to the object cache
|
||||||
|
* The patch is derived from the differences between the given object and its version in the object cache
|
||||||
|
* @param {DSpaceObject} ePerson The given object
|
||||||
|
*/
|
||||||
|
updateEPerson(ePerson: EPerson): Observable<RemoteData<EPerson>> {
|
||||||
|
const oldVersion$ = this.findByHref(ePerson._links.self.href);
|
||||||
|
return oldVersion$.pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
mergeMap((oldEPerson: EPerson) => {
|
||||||
|
const operations = this.generateOperations(oldEPerson, ePerson);
|
||||||
|
const patchRequest = new PatchRequest(this.requestService.generateRequestId(), ePerson._links.self.href, operations);
|
||||||
|
this.requestService.configure(patchRequest);
|
||||||
|
return this.findByHref(ePerson._links.self.href);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata operations are generated by the difference between old and new EPerson
|
||||||
|
* Custom replace operations for the other EPerson values
|
||||||
|
* @param oldEPerson
|
||||||
|
* @param newEPerson
|
||||||
|
*/
|
||||||
|
generateOperations(oldEPerson: EPerson, newEPerson: EPerson): Operation[] {
|
||||||
|
let operations = this.comparator.diff(oldEPerson, newEPerson).filter((operation: Operation) => operation.op === 'replace');
|
||||||
|
if (hasValue(oldEPerson.email) && oldEPerson.email !== newEPerson.email) {
|
||||||
|
operations = [...operations, {
|
||||||
|
op: 'replace', path: '/email', value: newEPerson.email
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
if (hasValue(oldEPerson.requireCertificate) && oldEPerson.requireCertificate !== newEPerson.requireCertificate) {
|
||||||
|
operations = [...operations, {
|
||||||
|
op: 'replace', path: '/certificate', value: newEPerson.requireCertificate
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
if (hasValue(oldEPerson.canLogIn) && oldEPerson.canLogIn !== newEPerson.canLogIn) {
|
||||||
|
operations = [...operations, {
|
||||||
|
op: 'replace', path: '/canLogIn', value: newEPerson.canLogIn
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
if (hasValue(oldEPerson.netid) && oldEPerson.netid !== newEPerson.netid) {
|
||||||
|
operations = [...operations, {
|
||||||
|
op: 'replace', path: '/netid', value: newEPerson.netid
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
return operations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method that clears a cached EPerson request and returns its REST url
|
||||||
|
*/
|
||||||
|
public clearEPersonRequests(): void {
|
||||||
|
this.getBrowseEndpoint().pipe(take(1)).subscribe((link: string) => {
|
||||||
|
this.requestService.removeByHrefSubstring(link);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to retrieve the eperson that is currently being edited
|
||||||
|
*/
|
||||||
|
public getActiveEPerson(): Observable<EPerson> {
|
||||||
|
return this.store.pipe(select(editEPersonSelector))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to cancel editing an EPerson, dispatches a cancel EPerson action
|
||||||
|
*/
|
||||||
|
public cancelEditEPerson() {
|
||||||
|
this.store.dispatch(new EPeopleRegistryCancelEPersonAction());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to set the EPerson being edited, dispatches an edit EPerson action
|
||||||
|
* @param ePerson The EPerson to edit
|
||||||
|
*/
|
||||||
|
public editEPerson(ePerson: EPerson) {
|
||||||
|
this.store.dispatch(new EPeopleRegistryEditEPersonAction(ePerson));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to delete an EPerson
|
||||||
|
* @param id The EPerson to delete
|
||||||
|
*/
|
||||||
|
public deleteEPerson(ePerson: EPerson): Observable<boolean> {
|
||||||
|
return this.delete(ePerson);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user