mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
Created separate pages for edit & create EPersons
This commit is contained in:
@@ -1,6 +1,16 @@
|
||||
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
||||
import { getAccessControlModuleRoute } from '../app-routing-paths';
|
||||
|
||||
export const EPERSON_PATH = 'epeople';
|
||||
|
||||
export function getEPersonsRoute(): string {
|
||||
return new URLCombiner(getAccessControlModuleRoute(), EPERSON_PATH).toString();
|
||||
}
|
||||
|
||||
export function getEPersonEditRoute(id: string): string {
|
||||
return new URLCombiner(getEPersonsRoute(), id).toString();
|
||||
}
|
||||
|
||||
export const GROUP_EDIT_PATH = 'groups';
|
||||
|
||||
export function getGroupsRoute() {
|
||||
|
@@ -3,7 +3,7 @@ import { RouterModule } from '@angular/router';
|
||||
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';
|
||||
import { GROUP_EDIT_PATH } from './access-control-routing-paths';
|
||||
import { EPERSON_PATH, GROUP_EDIT_PATH } from './access-control-routing-paths';
|
||||
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { GroupPageGuard } from './group-registry/group-page.guard';
|
||||
import {
|
||||
@@ -13,12 +13,14 @@ import {
|
||||
SiteAdministratorGuard
|
||||
} from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard';
|
||||
import { BulkAccessComponent } from './bulk-access/bulk-access.component';
|
||||
import { EPersonFormComponent } from './epeople-registry/eperson-form/eperson-form.component';
|
||||
import { EPersonResolver } from './epeople-registry/eperson-resolver.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: 'epeople',
|
||||
path: EPERSON_PATH,
|
||||
component: EPeopleRegistryComponent,
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver
|
||||
@@ -26,6 +28,25 @@ import { BulkAccessComponent } from './bulk-access/bulk-access.component';
|
||||
data: { title: 'admin.access-control.epeople.title', breadcrumbKey: 'admin.access-control.epeople' },
|
||||
canActivate: [SiteAdministratorGuard]
|
||||
},
|
||||
{
|
||||
path: `${EPERSON_PATH}/create`,
|
||||
component: EPersonFormComponent,
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
},
|
||||
data: { title: 'admin.access-control.epeople.add.title', breadcrumbKey: 'admin.access-control.epeople.add' },
|
||||
canActivate: [SiteAdministratorGuard],
|
||||
},
|
||||
{
|
||||
path: `${EPERSON_PATH}/:id`,
|
||||
component: EPersonFormComponent,
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
ePerson: EPersonResolver,
|
||||
},
|
||||
data: { title: 'admin.access-control.epeople.edit.title', breadcrumbKey: 'admin.access-control.epeople.edit' },
|
||||
canActivate: [SiteAdministratorGuard],
|
||||
},
|
||||
{
|
||||
path: GROUP_EDIT_PATH,
|
||||
component: GroupsRegistryComponent,
|
||||
|
@@ -4,96 +4,91 @@
|
||||
<div class="d-flex justify-content-between border-bottom mb-3">
|
||||
<h2 id="header" class="pb-2">{{labelPrefix + 'head' | translate}}</h2>
|
||||
|
||||
<div *ngIf="!isEPersonFormShown">
|
||||
<div>
|
||||
<button class="mr-auto btn btn-success addEPerson-button"
|
||||
(click)="isEPersonFormShown = true">
|
||||
[routerLink]="'create'">
|
||||
<i class="fas fa-plus"></i>
|
||||
<span class="d-none d-sm-inline ml-1">{{labelPrefix + 'button.add' | translate}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ds-eperson-form *ngIf="isEPersonFormShown" (submitForm)="reset()"
|
||||
(cancelForm)="isEPersonFormShown = false"></ds-eperson-form>
|
||||
<h3 id="search" class="border-bottom pb-2">{{labelPrefix + 'search.head' | translate}}
|
||||
|
||||
<div *ngIf="!isEPersonFormShown">
|
||||
<h3 id="search" class="border-bottom pb-2">{{labelPrefix + 'search.head' | translate}}
|
||||
|
||||
</h3>
|
||||
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
|
||||
<div>
|
||||
<select name="scope" id="scope" formControlName="scope" class="form-control" aria-label="Search scope">
|
||||
<option value="metadata">{{labelPrefix + 'search.scope.metadata' | translate}}</option>
|
||||
<option value="email">{{labelPrefix + 'search.scope.email' | translate}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-grow-1 mr-3 ml-3">
|
||||
<div class="form-group input-group">
|
||||
<input type="text" name="query" id="query" formControlName="query"
|
||||
class="form-control" [attr.aria-label]="labelPrefix + 'search.placeholder' | translate"
|
||||
[placeholder]="(labelPrefix + 'search.placeholder' | translate)">
|
||||
<span class="input-group-append">
|
||||
</h3>
|
||||
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
|
||||
<div>
|
||||
<select name="scope" id="scope" formControlName="scope" class="form-control" aria-label="Search scope">
|
||||
<option value="metadata">{{labelPrefix + 'search.scope.metadata' | translate}}</option>
|
||||
<option value="email">{{labelPrefix + 'search.scope.email' | translate}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-grow-1 mr-3 ml-3">
|
||||
<div class="form-group input-group">
|
||||
<input type="text" name="query" id="query" formControlName="query"
|
||||
class="form-control" [attr.aria-label]="labelPrefix + 'search.placeholder' | translate"
|
||||
[placeholder]="(labelPrefix + 'search.placeholder' | translate)">
|
||||
<span class="input-group-append">
|
||||
<button type="submit" class="search-button btn btn-primary">
|
||||
<i class="fas fa-search"></i> {{ labelPrefix + 'search.button' | translate }}
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button (click)="clearFormAndResetResult();"
|
||||
class="search-button btn btn-secondary">{{labelPrefix + 'button.see-all' | translate}}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<ds-themed-loading *ngIf="searching$ | async"></ds-themed-loading>
|
||||
<ds-pagination
|
||||
*ngIf="(pageInfoState$ | async)?.totalElements > 0 && !(searching$ | async)"
|
||||
[paginationOptions]="config"
|
||||
[pageInfoState]="pageInfoState$"
|
||||
[collectionSize]="(pageInfoState$ | async)?.totalElements"
|
||||
[hideGear]="true"
|
||||
[hidePagerWhenSinglePage]="true">
|
||||
|
||||
<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 epersonDto of (ePeopleDto$ | async)?.page"
|
||||
[ngClass]="{'table-primary' : isActive(epersonDto.eperson) | async}">
|
||||
<td>{{epersonDto.eperson.id}}</td>
|
||||
<td>{{ dsoNameService.getName(epersonDto.eperson) }}</td>
|
||||
<td>{{epersonDto.eperson.email}}</td>
|
||||
<td>
|
||||
<div class="btn-group edit-field">
|
||||
<button (click)="toggleEditEPerson(epersonDto.eperson)"
|
||||
class="btn btn-outline-primary btn-sm access-control-editEPersonButton"
|
||||
title="{{labelPrefix + 'table.edit.buttons.edit' | translate: { name: dsoNameService.getName(epersonDto.eperson) } }}">
|
||||
<i class="fas fa-edit fa-fw"></i>
|
||||
</button>
|
||||
<button [disabled]="!epersonDto.ableToDelete" (click)="deleteEPerson(epersonDto.eperson)"
|
||||
class="delete-button btn btn-outline-danger btn-sm access-control-deleteEPersonButton"
|
||||
title="{{labelPrefix + 'table.edit.buttons.remove' | translate: { name: dsoNameService.getName(epersonDto.eperson) } }}">
|
||||
<i class="fas fa-trash-alt fa-fw"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</ds-pagination>
|
||||
|
||||
<div *ngIf="(pageInfoState$ | async)?.totalElements == 0" class="alert alert-info w-100 mb-2" role="alert">
|
||||
{{labelPrefix + 'no-items' | translate}}
|
||||
</div>
|
||||
<div>
|
||||
<button (click)="clearFormAndResetResult();"
|
||||
class="search-button btn btn-secondary">{{labelPrefix + 'button.see-all' | translate}}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<ds-themed-loading *ngIf="searching$ | async"></ds-themed-loading>
|
||||
<ds-pagination
|
||||
*ngIf="(pageInfoState$ | async)?.totalElements > 0 && !(searching$ | async)"
|
||||
[paginationOptions]="config"
|
||||
[pageInfoState]="pageInfoState$"
|
||||
[collectionSize]="(pageInfoState$ | async)?.totalElements"
|
||||
[hideGear]="true"
|
||||
[hidePagerWhenSinglePage]="true">
|
||||
|
||||
<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 epersonDto of (ePeopleDto$ | async)?.page"
|
||||
[ngClass]="{'table-primary' : isActive(epersonDto.eperson) | async}">
|
||||
<td>{{epersonDto.eperson.id}}</td>
|
||||
<td>{{ dsoNameService.getName(epersonDto.eperson) }}</td>
|
||||
<td>{{epersonDto.eperson.email}}</td>
|
||||
<td>
|
||||
<div class="btn-group edit-field">
|
||||
<button [routerLink]="getEditEPeoplePage(epersonDto.eperson.id)"
|
||||
class="btn btn-outline-primary btn-sm access-control-editEPersonButton"
|
||||
title="{{labelPrefix + 'table.edit.buttons.edit' | translate: { name: dsoNameService.getName(epersonDto.eperson) } }}">
|
||||
<i class="fas fa-edit fa-fw"></i>
|
||||
</button>
|
||||
<button [disabled]="!epersonDto.ableToDelete" (click)="deleteEPerson(epersonDto.eperson)"
|
||||
class="delete-button btn btn-outline-danger btn-sm access-control-deleteEPersonButton"
|
||||
title="{{labelPrefix + 'table.edit.buttons.remove' | translate: { name: dsoNameService.getName(epersonDto.eperson) } }}">
|
||||
<i class="fas fa-trash-alt fa-fw"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</ds-pagination>
|
||||
|
||||
<div *ngIf="(pageInfoState$ | async)?.totalElements == 0" class="alert alert-info w-100 mb-2" role="alert">
|
||||
{{labelPrefix + 'no-items' | translate}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -203,36 +203,6 @@ describe('EPeopleRegistryComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleEditEPerson', () => {
|
||||
describe('when you click on first edit eperson button', () => {
|
||||
beforeEach(fakeAsync(() => {
|
||||
const editButtons = fixture.debugElement.queryAll(By.css('.access-control-editEPersonButton'));
|
||||
editButtons[0].triggerEventHandler('click', {
|
||||
preventDefault: () => {/**/
|
||||
}
|
||||
});
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('editEPerson form is toggled', () => {
|
||||
const ePeopleIds = fixture.debugElement.queryAll(By.css('#epeople tr td:first-child'));
|
||||
ePersonDataServiceStub.getActiveEPerson().subscribe((activeEPerson: EPerson) => {
|
||||
if (ePeopleIds[0] && activeEPerson === ePeopleIds[0].nativeElement.textContent) {
|
||||
expect(component.isEPersonFormShown).toEqual(false);
|
||||
} else {
|
||||
expect(component.isEPersonFormShown).toEqual(true);
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
it('EPerson search section is hidden', () => {
|
||||
expect(fixture.debugElement.query(By.css('#search'))).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteEPerson', () => {
|
||||
describe('when you click on first delete eperson button', () => {
|
||||
let ePeopleIdsFoundBeforeDelete;
|
||||
|
@@ -22,6 +22,7 @@ import { PageInfo } from '../../core/shared/page-info.model';
|
||||
import { NoContent } from '../../core/shared/NoContent.model';
|
||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||
import { getEPersonEditRoute, getEPersonsRoute } from '../access-control-routing-paths';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-epeople-registry',
|
||||
@@ -64,11 +65,6 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
|
||||
currentPage: 1
|
||||
});
|
||||
|
||||
/**
|
||||
* Whether or not to show the EPerson form
|
||||
*/
|
||||
isEPersonFormShown: boolean;
|
||||
|
||||
// The search form
|
||||
searchForm;
|
||||
|
||||
@@ -114,17 +110,11 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
initialisePage() {
|
||||
this.searching$.next(true);
|
||||
this.isEPersonFormShown = false;
|
||||
this.search({scope: this.currentSearchScope, query: this.currentSearchQuery});
|
||||
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
|
||||
if (eperson != null && eperson.id) {
|
||||
this.isEPersonFormShown = true;
|
||||
}
|
||||
}));
|
||||
this.subs.push(this.ePeople$.pipe(
|
||||
switchMap((epeople: PaginatedList<EPerson>) => {
|
||||
if (epeople.pageInfo.totalElements > 0) {
|
||||
return combineLatest([...epeople.page.map((eperson: EPerson) => {
|
||||
return combineLatest(epeople.page.map((eperson: EPerson) => {
|
||||
return this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined).pipe(
|
||||
map((authorized) => {
|
||||
const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel();
|
||||
@@ -133,7 +123,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
|
||||
return epersonDtoModel;
|
||||
})
|
||||
);
|
||||
})]).pipe(map((dtos: EpersonDtoModel[]) => {
|
||||
})).pipe(map((dtos: EpersonDtoModel[]) => {
|
||||
return buildPaginatedList(epeople.pageInfo, dtos);
|
||||
}));
|
||||
} else {
|
||||
@@ -160,14 +150,14 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
|
||||
const query: string = data.query;
|
||||
const scope: string = data.scope;
|
||||
if (query != null && this.currentSearchQuery !== query) {
|
||||
this.router.navigate([this.epersonService.getEPeoplePageRouterLink()], {
|
||||
void this.router.navigate([getEPersonsRoute()], {
|
||||
queryParamsHandling: 'merge'
|
||||
});
|
||||
this.currentSearchQuery = query;
|
||||
this.paginationService.resetPage(this.config.id);
|
||||
}
|
||||
if (scope != null && this.currentSearchScope !== scope) {
|
||||
this.router.navigate([this.epersonService.getEPeoplePageRouterLink()], {
|
||||
void this.router.navigate([getEPersonsRoute()], {
|
||||
queryParamsHandling: 'merge'
|
||||
});
|
||||
this.currentSearchScope = scope;
|
||||
@@ -205,23 +195,6 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
|
||||
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
|
||||
*/
|
||||
@@ -264,16 +237,6 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
|
||||
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||
}
|
||||
|
||||
scrollToTop() {
|
||||
(function smoothscroll() {
|
||||
const currentScroll = document.documentElement.scrollTop || document.body.scrollTop;
|
||||
if (currentScroll > 0) {
|
||||
window.requestAnimationFrame(smoothscroll);
|
||||
window.scrollTo(0, currentScroll - (currentScroll / 8));
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all input-fields to be empty and search all search
|
||||
*/
|
||||
@@ -284,20 +247,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
|
||||
this.search({query: ''});
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will set everything to stale, which will cause the lists on this page to update.
|
||||
*/
|
||||
reset(): void {
|
||||
this.epersonService.getBrowseEndpoint().pipe(
|
||||
take(1),
|
||||
switchMap((href: string) => {
|
||||
return this.requestService.setStaleByHrefSubstring(href).pipe(
|
||||
take(1),
|
||||
);
|
||||
})
|
||||
).subscribe(()=>{
|
||||
this.epersonService.cancelEditEPerson();
|
||||
this.isEPersonFormShown = false;
|
||||
});
|
||||
getEditEPeoplePage(id: string): string {
|
||||
return getEPersonEditRoute(id);
|
||||
}
|
||||
}
|
||||
|
@@ -1,89 +1,98 @@
|
||||
<div *ngIf="epersonService.getActiveEPerson() | async; then editheader; else createHeader"></div>
|
||||
<div class="container">
|
||||
<div class="group-form row">
|
||||
<div class="col-12">
|
||||
|
||||
<ng-template #createHeader>
|
||||
<h4>{{messagePrefix + '.create' | translate}}</h4>
|
||||
</ng-template>
|
||||
<div *ngIf="epersonService.getActiveEPerson() | async; then editHeader; else createHeader"></div>
|
||||
|
||||
<ng-template #editheader>
|
||||
<h4>{{messagePrefix + '.edit' | translate}}</h4>
|
||||
</ng-template>
|
||||
<ng-template #createHeader>
|
||||
<h2 class="border-bottom pb-2">{{messagePrefix + '.create' | translate}}</h2>
|
||||
</ng-template>
|
||||
|
||||
<ds-form [formId]="formId"
|
||||
[formModel]="formModel"
|
||||
[formGroup]="formGroup"
|
||||
[formLayout]="formLayout"
|
||||
[displayCancel]="false"
|
||||
[submitLabel]="submitLabel"
|
||||
(submitForm)="onSubmit()">
|
||||
<div before class="btn-group">
|
||||
<button (click)="onCancel()"
|
||||
class="btn btn-outline-secondary"><i class="fas fa-arrow-left"></i> {{messagePrefix + '.return' | translate}}</button>
|
||||
</div>
|
||||
<div *ngIf="displayResetPassword" between class="btn-group">
|
||||
<button class="btn btn-primary" [disabled]="!(canReset$ | async)" (click)="resetPassword()">
|
||||
<i class="fa fa-key"></i> {{'admin.access-control.epeople.actions.reset' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
<div between class="btn-group ml-1">
|
||||
<button *ngIf="!isImpersonated" class="btn btn-primary" [ngClass]="{'d-none' : !(canImpersonate$ | async)}" (click)="impersonate()">
|
||||
<i class="fa fa-user-secret"></i> {{'admin.access-control.epeople.actions.impersonate' | translate}}
|
||||
</button>
|
||||
<button *ngIf="isImpersonated" class="btn btn-primary" (click)="stopImpersonating()">
|
||||
<i class="fa fa-user-secret"></i> {{'admin.access-control.epeople.actions.stop-impersonating' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
<button after class="btn btn-danger delete-button" [disabled]="!(canDelete$ | async)" (click)="delete()">
|
||||
<i class="fas fa-trash"></i> {{'admin.access-control.epeople.actions.delete' | translate}}
|
||||
</button>
|
||||
</ds-form>
|
||||
<ng-template #editHeader>
|
||||
<h2 class="border-bottom pb-2">{{messagePrefix + '.edit' | translate}}</h2>
|
||||
</ng-template>
|
||||
|
||||
<ds-themed-loading [showMessage]="false" *ngIf="!formGroup"></ds-themed-loading>
|
||||
<ds-form [formId]="formId"
|
||||
[formModel]="formModel"
|
||||
[formGroup]="formGroup"
|
||||
[formLayout]="formLayout"
|
||||
[displayCancel]="false"
|
||||
[submitLabel]="submitLabel"
|
||||
(submitForm)="onSubmit()">
|
||||
<div before class="btn-group">
|
||||
<button (click)="onCancel()" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-left"></i> {{messagePrefix + '.return' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="displayResetPassword" between class="btn-group">
|
||||
<button class="btn btn-primary" [disabled]="!(canReset$ | async)" (click)="resetPassword()">
|
||||
<i class="fa fa-key"></i> {{'admin.access-control.epeople.actions.reset' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
<div between class="btn-group ml-1">
|
||||
<button *ngIf="!isImpersonated" class="btn btn-primary" [ngClass]="{'d-none' : !(canImpersonate$ | async)}"
|
||||
(click)="impersonate()">
|
||||
<i class="fa fa-user-secret"></i> {{'admin.access-control.epeople.actions.impersonate' | translate}}
|
||||
</button>
|
||||
<button *ngIf="isImpersonated" class="btn btn-primary" (click)="stopImpersonating()">
|
||||
<i class="fa fa-user-secret"></i> {{'admin.access-control.epeople.actions.stop-impersonating' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
<button after class="btn btn-danger delete-button" [disabled]="!(canDelete$ | async)" (click)="delete()">
|
||||
<i class="fas fa-trash"></i> {{'admin.access-control.epeople.actions.delete' | translate}}
|
||||
</button>
|
||||
</ds-form>
|
||||
|
||||
<div *ngIf="epersonService.getActiveEPerson() | async">
|
||||
<h5>{{messagePrefix + '.groupsEPersonIsMemberOf' | translate}}</h5>
|
||||
<ds-themed-loading [showMessage]="false" *ngIf="!formGroup"></ds-themed-loading>
|
||||
|
||||
<ds-themed-loading [showMessage]="false" *ngIf="!(groups | async)"></ds-themed-loading>
|
||||
<div *ngIf="epersonService.getActiveEPerson() | async">
|
||||
<h5>{{messagePrefix + '.groupsEPersonIsMemberOf' | translate}}</h5>
|
||||
|
||||
<ds-pagination
|
||||
*ngIf="(groups | async)?.payload?.totalElements > 0"
|
||||
[paginationOptions]="config"
|
||||
[pageInfoState]="(groups | async)?.payload"
|
||||
[collectionSize]="(groups | async)?.payload?.totalElements"
|
||||
[hideGear]="true"
|
||||
[hidePagerWhenSinglePage]="true"
|
||||
(pageChange)="onPageChange($event)">
|
||||
<ds-themed-loading [showMessage]="false" *ngIf="!(groups | async)"></ds-themed-loading>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table id="groups" class="table table-striped table-hover table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.id' | translate}}</th>
|
||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.name' | translate}}</th>
|
||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.collectionOrCommunity' | translate}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let group of (groups | async)?.payload?.page">
|
||||
<td class="align-middle">{{group.id}}</td>
|
||||
<td class="align-middle">
|
||||
<a (click)="groupsDataService.startEditingNewGroup(group)"
|
||||
[routerLink]="[groupsDataService.getGroupEditPageRouterLink(group)]">
|
||||
{{ dsoNameService.getName(group) }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="align-middle">{{ dsoNameService.getName(undefined) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<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)">
|
||||
|
||||
</ds-pagination>
|
||||
<div class="table-responsive">
|
||||
<table id="groups" class="table table-striped table-hover table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.id' | translate}}</th>
|
||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.name' | translate}}</th>
|
||||
<th scope="col" class="align-middle">{{messagePrefix + '.table.collectionOrCommunity' | translate}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let group of (groups | async)?.payload?.page">
|
||||
<td class="align-middle">{{group.id}}</td>
|
||||
<td class="align-middle">
|
||||
<a (click)="groupsDataService.startEditingNewGroup(group)"
|
||||
[routerLink]="[groupsDataService.getGroupEditPageRouterLink(group)]">
|
||||
{{ dsoNameService.getName(group) }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="align-middle">{{ dsoNameService.getName(undefined) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div *ngIf="(groups | async)?.payload?.totalElements == 0" class="alert alert-info w-100 mb-2" role="alert">
|
||||
<div>{{messagePrefix + '.memberOfNoGroups' | translate}}</div>
|
||||
<div>
|
||||
<button [routerLink]="[groupsDataService.getGroupRegistryRouterLink()]"
|
||||
class="btn btn-primary">{{messagePrefix + '.goToGroups' | translate}}</button>
|
||||
</ds-pagination>
|
||||
|
||||
<div *ngIf="(groups | async)?.payload?.totalElements == 0" class="alert alert-info w-100 mb-2" role="alert">
|
||||
<div>{{messagePrefix + '.memberOfNoGroups' | translate}}</div>
|
||||
<div>
|
||||
<button [routerLink]="[groupsDataService.getGroupRegistryRouterLink()]"
|
||||
class="btn btn-primary">{{messagePrefix + '.goToGroups' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -31,6 +31,10 @@ import { PaginationServiceStub } from '../../../shared/testing/pagination-servic
|
||||
import { FindListOptions } from '../../../core/data/find-list-options.model';
|
||||
import { ValidateEmailNotTaken } from './validators/email-taken.validator';
|
||||
import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service';
|
||||
import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { RouterStub } from '../../../shared/testing/router.stub';
|
||||
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
||||
|
||||
describe('EPersonFormComponent', () => {
|
||||
let component: EPersonFormComponent;
|
||||
@@ -43,6 +47,8 @@ describe('EPersonFormComponent', () => {
|
||||
let authorizationService: AuthorizationDataService;
|
||||
let groupsDataService: GroupDataService;
|
||||
let epersonRegistrationService: EpersonRegistrationService;
|
||||
let route: ActivatedRouteStub;
|
||||
let router: RouterStub;
|
||||
|
||||
let paginationService;
|
||||
|
||||
@@ -106,6 +112,9 @@ describe('EPersonFormComponent', () => {
|
||||
},
|
||||
getEPersonByEmail(email): Observable<RemoteData<EPerson>> {
|
||||
return createSuccessfulRemoteDataObject$(null);
|
||||
},
|
||||
findById(_id: string, _useCachedVersionIfAvailable = true, _reRequestOnStale = true, ..._linksToFollow: FollowLinkConfig<EPerson>[]): Observable<RemoteData<EPerson>> {
|
||||
return createSuccessfulRemoteDataObject$(null);
|
||||
}
|
||||
};
|
||||
builderService = Object.assign(getMockFormBuilderService(),{
|
||||
@@ -182,6 +191,8 @@ describe('EPersonFormComponent', () => {
|
||||
});
|
||||
|
||||
paginationService = new PaginationServiceStub();
|
||||
route = new ActivatedRouteStub();
|
||||
router = new RouterStub();
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
|
||||
TranslateModule.forRoot({
|
||||
@@ -202,6 +213,8 @@ describe('EPersonFormComponent', () => {
|
||||
{ provide: PaginationService, useValue: paginationService },
|
||||
{ provide: RequestService, useValue: jasmine.createSpyObj('requestService', ['removeByHrefSubstring'])},
|
||||
{ provide: EpersonRegistrationService, useValue: epersonRegistrationService },
|
||||
{ provide: ActivatedRoute, useValue: route },
|
||||
{ provide: Router, useValue: router },
|
||||
EPeopleRegistryComponent
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
@@ -263,24 +276,18 @@ describe('EPersonFormComponent', () => {
|
||||
fixture.detectChanges();
|
||||
});
|
||||
describe('firstName, lastName and email should be required', () => {
|
||||
it('form should be invalid because the firstName is required', waitForAsync(() => {
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.formGroup.controls.firstName.valid).toBeFalse();
|
||||
expect(component.formGroup.controls.firstName.errors.required).toBeTrue();
|
||||
});
|
||||
}));
|
||||
it('form should be invalid because the lastName is required', waitForAsync(() => {
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.formGroup.controls.lastName.valid).toBeFalse();
|
||||
expect(component.formGroup.controls.lastName.errors.required).toBeTrue();
|
||||
});
|
||||
}));
|
||||
it('form should be invalid because the email is required', waitForAsync(() => {
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.formGroup.controls.email.valid).toBeFalse();
|
||||
expect(component.formGroup.controls.email.errors.required).toBeTrue();
|
||||
});
|
||||
}));
|
||||
it('form should be invalid because the firstName is required', () => {
|
||||
expect(component.formGroup.controls.firstName.valid).toBeFalse();
|
||||
expect(component.formGroup.controls.firstName.errors.required).toBeTrue();
|
||||
});
|
||||
it('form should be invalid because the lastName is required', () => {
|
||||
expect(component.formGroup.controls.lastName.valid).toBeFalse();
|
||||
expect(component.formGroup.controls.lastName.errors.required).toBeTrue();
|
||||
});
|
||||
it('form should be invalid because the email is required', () => {
|
||||
expect(component.formGroup.controls.email.valid).toBeFalse();
|
||||
expect(component.formGroup.controls.email.errors.required).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
describe('after inserting information firstName,lastName and email not required', () => {
|
||||
@@ -290,24 +297,18 @@ describe('EPersonFormComponent', () => {
|
||||
component.formGroup.controls.email.setValue('test@test.com');
|
||||
fixture.detectChanges();
|
||||
});
|
||||
it('firstName should be valid because the firstName is set', waitForAsync(() => {
|
||||
fixture.whenStable().then(() => {
|
||||
it('firstName should be valid because the firstName is set', () => {
|
||||
expect(component.formGroup.controls.firstName.valid).toBeTrue();
|
||||
expect(component.formGroup.controls.firstName.errors).toBeNull();
|
||||
});
|
||||
}));
|
||||
it('lastName should be valid because the lastName is set', waitForAsync(() => {
|
||||
fixture.whenStable().then(() => {
|
||||
});
|
||||
it('lastName should be valid because the lastName is set', () => {
|
||||
expect(component.formGroup.controls.lastName.valid).toBeTrue();
|
||||
expect(component.formGroup.controls.lastName.errors).toBeNull();
|
||||
});
|
||||
}));
|
||||
it('email should be valid because the email is set', waitForAsync(() => {
|
||||
fixture.whenStable().then(() => {
|
||||
});
|
||||
it('email should be valid because the email is set', () => {
|
||||
expect(component.formGroup.controls.email.valid).toBeTrue();
|
||||
expect(component.formGroup.controls.email.errors).toBeNull();
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -316,12 +317,10 @@ describe('EPersonFormComponent', () => {
|
||||
component.formGroup.controls.email.setValue('test@test');
|
||||
fixture.detectChanges();
|
||||
});
|
||||
it('email should not be valid because the email pattern', waitForAsync(() => {
|
||||
fixture.whenStable().then(() => {
|
||||
it('email should not be valid because the email pattern', () => {
|
||||
expect(component.formGroup.controls.email.valid).toBeFalse();
|
||||
expect(component.formGroup.controls.email.errors.pattern).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('after already utilized email', () => {
|
||||
@@ -336,12 +335,10 @@ describe('EPersonFormComponent', () => {
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('email should not be valid because email is already taken', waitForAsync(() => {
|
||||
fixture.whenStable().then(() => {
|
||||
it('email should not be valid because email is already taken', () => {
|
||||
expect(component.formGroup.controls.email.valid).toBeFalse();
|
||||
expect(component.formGroup.controls.email.errors.emailTaken).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -393,11 +390,9 @@ describe('EPersonFormComponent', () => {
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should emit a new eperson using the correct values', waitForAsync(() => {
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.submitForm.emit).toHaveBeenCalledWith(expected);
|
||||
});
|
||||
}));
|
||||
it('should emit a new eperson using the correct values', () => {
|
||||
expect(component.submitForm.emit).toHaveBeenCalledWith(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with an active eperson', () => {
|
||||
@@ -428,11 +423,9 @@ describe('EPersonFormComponent', () => {
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should emit the existing eperson using the correct values', waitForAsync(() => {
|
||||
fixture.whenStable().then(() => {
|
||||
expect(component.submitForm.emit).toHaveBeenCalledWith(expectedWithId);
|
||||
});
|
||||
}));
|
||||
it('should emit the existing eperson using the correct values', () => {
|
||||
expect(component.submitForm.emit).toHaveBeenCalledWith(expectedWithId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -38,6 +38,8 @@ import { Registration } from '../../../core/shared/registration.model';
|
||||
import { EpersonRegistrationService } from '../../../core/data/eperson-registration.service';
|
||||
import { TYPE_REQUEST_FORGOT } from '../../../register-email-form/register-email-form.component';
|
||||
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { getEPersonsRoute } from '../../access-control-routing-paths';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-eperson-form',
|
||||
@@ -194,6 +196,8 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
||||
public requestService: RequestService,
|
||||
private epersonRegistrationService: EpersonRegistrationService,
|
||||
public dsoNameService: DSONameService,
|
||||
protected route: ActivatedRoute,
|
||||
protected router: Router,
|
||||
) {
|
||||
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
|
||||
this.epersonInitial = eperson;
|
||||
@@ -213,7 +217,9 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
||||
* This method will initialise the page
|
||||
*/
|
||||
initialisePage() {
|
||||
|
||||
this.subs.push(this.epersonService.findById(this.route.snapshot.params.id).subscribe((ePersonRD: RemoteData<EPerson>) => {
|
||||
this.epersonService.editEPerson(ePersonRD.payload);
|
||||
}));
|
||||
observableCombineLatest([
|
||||
this.translateService.get(`${this.messagePrefix}.firstName`),
|
||||
this.translateService.get(`${this.messagePrefix}.lastName`),
|
||||
@@ -339,6 +345,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
||||
onCancel() {
|
||||
this.epersonService.cancelEditEPerson();
|
||||
this.cancelForm.emit();
|
||||
void this.router.navigate([getEPersonsRoute()]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -390,6 +397,8 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
||||
if (rd.hasSucceeded) {
|
||||
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.created.success', { name: this.dsoNameService.getName(ePersonToCreate) }));
|
||||
this.submitForm.emit(ePersonToCreate);
|
||||
this.epersonService.clearEPersonRequests();
|
||||
void this.router.navigateByUrl(getEPersonsRoute());
|
||||
} else {
|
||||
this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.created.failure', { name: this.dsoNameService.getName(ePersonToCreate) }));
|
||||
this.cancelForm.emit();
|
||||
@@ -429,6 +438,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
||||
if (rd.hasSucceeded) {
|
||||
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.edited.success', { name: this.dsoNameService.getName(editedEperson) }));
|
||||
this.submitForm.emit(editedEperson);
|
||||
void this.router.navigateByUrl(getEPersonsRoute());
|
||||
} else {
|
||||
this.notificationsService.error(this.translateService.get(this.labelPrefix + 'notification.edited.failure', { name: this.dsoNameService.getName(editedEperson) }));
|
||||
this.cancelForm.emit();
|
||||
@@ -495,6 +505,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
||||
).subscribe(({ restResponse, eperson }: { restResponse: RemoteData<NoContent> | null, eperson: EPerson }) => {
|
||||
if (restResponse?.hasSucceeded) {
|
||||
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', { name: this.dsoNameService.getName(eperson) }));
|
||||
void this.router.navigate([getEPersonsRoute()]);
|
||||
} else {
|
||||
this.notificationsService.error(`Error occurred when trying to delete EPerson with id: ${eperson?.id} with code: ${restResponse?.statusCode} and message: ${restResponse?.errorMessage}`);
|
||||
}
|
||||
@@ -541,16 +552,6 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will ensure that the page gets reset and that the cache is cleared
|
||||
*/
|
||||
reset() {
|
||||
this.epersonService.getActiveEPerson().pipe(take(1)).subscribe((eperson: EPerson) => {
|
||||
this.requestService.removeByHrefSubstring(eperson.self);
|
||||
});
|
||||
this.initialisePage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for the given ePerson if there is already an ePerson in the system with that email
|
||||
* and shows notification if this is the case
|
||||
|
@@ -0,0 +1,53 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { EPerson } from '../../core/eperson/models/eperson.model';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
|
||||
import { ResolvedAction } from '../../core/resolving/resolver.actions';
|
||||
import { EPersonDataService } from '../../core/eperson/eperson-data.service';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
|
||||
export const EPERSON_EDIT_FOLLOW_LINKS: FollowLinkConfig<EPerson>[] = [
|
||||
followLink('groups'),
|
||||
];
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific {@link EPerson} before the route is activated
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class EPersonResolver implements Resolve<RemoteData<EPerson>> {
|
||||
|
||||
constructor(
|
||||
protected ePersonService: EPersonDataService,
|
||||
protected store: Store<any>,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving a {@link EPerson} based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns `Observable<<RemoteData<EPerson>>` Emits the found {@link EPerson} based on the parameters in the current
|
||||
* route, or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<EPerson>> {
|
||||
const ePersonRD$: Observable<RemoteData<EPerson>> = this.ePersonService.findById(route.params.id,
|
||||
true,
|
||||
false,
|
||||
...EPERSON_EDIT_FOLLOW_LINKS,
|
||||
).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
|
||||
ePersonRD$.subscribe((ePersonRD: RemoteData<EPerson>) => {
|
||||
this.store.dispatch(new ResolvedAction(state.url, ePersonRD.payload));
|
||||
});
|
||||
|
||||
return ePersonRD$;
|
||||
}
|
||||
|
||||
}
|
@@ -248,6 +248,14 @@
|
||||
|
||||
"admin.access-control.epeople.title": "EPeople",
|
||||
|
||||
"admin.access-control.epeople.edit.breadcrumbs": "New EPerson",
|
||||
|
||||
"admin.access-control.epeople.edit.title": "New EPerson",
|
||||
|
||||
"admin.access-control.epeople.add.breadcrumbs": "Add EPerson",
|
||||
|
||||
"admin.access-control.epeople.add.title": "Add EPerson",
|
||||
|
||||
"admin.access-control.epeople.head": "EPeople",
|
||||
|
||||
"admin.access-control.epeople.search.head": "Search",
|
||||
|
Reference in New Issue
Block a user