Added possibility to search for eperson or group while adding a new resource policy

This commit is contained in:
Giuseppe Digilio
2020-04-30 15:43:52 +02:00
parent 8635209959
commit b338332d13
11 changed files with 488 additions and 33 deletions

View File

@@ -2021,7 +2021,7 @@
"resource-policies.form.action-type.required": "You must select the resource policy action.",
"resource-policies.form.eperson-group-list.label": "Select the eperson or group that will be grant of the permission",
"resource-policies.form.eperson-group-list.label": "The eperson or group that will be grant of the permission",
"resource-policies.form.eperson-group-list.select.btn": "Select",

View File

@@ -1,4 +1,7 @@
<div class="mt-3">
<div class="mt-3" @fadeInOut>
<ds-eperson-search-box *ngIf="isListOfEPerson" (search)="onSearch($event)"></ds-eperson-search-box>
<ds-group-search-box *ngIf="!isListOfEPerson" (search)="onSearch($event)"></ds-group-search-box>
<ds-pagination *ngIf="(getList() | async)?.payload?.totalElements > 0"
[paginationOptions]="paginationOptions"
[collectionSize]="(getList() | async)?.payload?.totalElements"

View File

@@ -2,9 +2,8 @@ import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing'
import { ChangeDetectorRef, Component, Injector, NO_ERRORS_SCHEMA } from '@angular/core';
import { of as observableOf } from 'rxjs';
import { TestScheduler } from 'rxjs/testing';
import { TranslateModule } from '@ngx-translate/core';
import { cold, getTestScheduler } from 'jasmine-marbles';
import { cold } from 'jasmine-marbles';
import { uniqueId } from 'lodash';
import { createSuccessfulRemoteDataObject, createTestComponent } from '../../../testing/utils';
@@ -12,38 +11,42 @@ import { EPersonDataService } from '../../../../core/eperson/eperson-data.servic
import { GroupDataService } from '../../../../core/eperson/group-data.service';
import { RequestService } from '../../../../core/data/request.service';
import { getMockRequestService } from '../../../mocks/mock-request.service';
import { EpersonGroupListComponent } from './eperson-group-list.component';
import { EpersonGroupListComponent, SearchEvent } from './eperson-group-list.component';
import { EPersonMock } from '../../../testing/eperson-mock';
import { GroupMock } from '../../../testing/group-mock';
import { PaginationComponentOptions } from '../../../pagination/pagination-component-options.model';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { PageInfo } from '../../../../core/shared/page-info.model';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
describe('EpersonGroupListComponent test suite', () => {
let comp: EpersonGroupListComponent;
let compAsAny: any;
let fixture: ComponentFixture<EpersonGroupListComponent>;
let de;
let scheduler: TestScheduler;
let groupService: any;
let epersonService: any;
const paginationOptions: PaginationComponentOptions = new PaginationComponentOptions()
paginationOptions.id = uniqueId('eperson-group-list-pagination-test');
paginationOptions.pageSize = 5;
const epersonService = jasmine.createSpyObj('epersonService',
const mockEpersonService = jasmine.createSpyObj('epersonService',
{
findByHref: jasmine.createSpy('findByHref'),
findAll: jasmine.createSpy('findAll'),
searchByScope: jasmine.createSpy('searchByScope'),
},
{
linkPath: 'epersons'
}
);
const groupService = jasmine.createSpyObj('groupService',
const mockGroupService = jasmine.createSpyObj('groupService',
{
findByHref: jasmine.createSpy('findByHref'),
findAll: jasmine.createSpy('findAll'),
searchGroups: jasmine.createSpy('searchGroups'),
},
{
linkPath: 'groups'
@@ -59,6 +62,7 @@ describe('EpersonGroupListComponent test suite', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
NoopAnimationsModule,
TranslateModule.forRoot()
],
declarations: [
@@ -66,8 +70,8 @@ describe('EpersonGroupListComponent test suite', () => {
TestComponent
],
providers: [
{ provide: EPersonDataService, useValue: epersonService },
{ provide: GroupDataService, useValue: groupService },
{ provide: EPersonDataService, useValue: mockEpersonService },
{ provide: GroupDataService, useValue: mockGroupService },
{ provide: RequestService, useValue: getMockRequestService() },
EpersonGroupListComponent,
ChangeDetectorRef,
@@ -108,6 +112,7 @@ describe('EpersonGroupListComponent test suite', () => {
beforeEach(() => {
// initTestScheduler();
fixture = TestBed.createComponent(EpersonGroupListComponent);
epersonService = TestBed.get(EPersonDataService);
comp = fixture.componentInstance;
compAsAny = fixture.componentInstance;
comp.isListOfEPerson = true;
@@ -138,11 +143,8 @@ describe('EpersonGroupListComponent test suite', () => {
});
it('should init the list of eperson', () => {
compAsAny.dataService.findAll.and.returnValue(observableOf(epersonPaginatedListRD));
scheduler = getTestScheduler();
scheduler.schedule(() => comp.updateList(paginationOptions));
scheduler.flush();
epersonService.searchByScope.and.returnValue(observableOf(epersonPaginatedListRD));
fixture.detectChanges();
expect(compAsAny.list$.value).toEqual(epersonPaginatedListRD);
expect(comp.getList()).toBeObservable(cold('a', {
@@ -187,6 +189,7 @@ describe('EpersonGroupListComponent test suite', () => {
beforeEach(() => {
// initTestScheduler();
fixture = TestBed.createComponent(EpersonGroupListComponent);
groupService = TestBed.get(GroupDataService);
comp = fixture.componentInstance;
compAsAny = fixture.componentInstance;
comp.isListOfEPerson = false;
@@ -217,11 +220,8 @@ describe('EpersonGroupListComponent test suite', () => {
});
it('should init the list of group', () => {
compAsAny.dataService.findAll.and.returnValue(observableOf(groupPaginatedListRD));
scheduler = getTestScheduler();
scheduler.schedule(() => comp.updateList(paginationOptions));
scheduler.flush();
groupService.searchGroups.and.returnValue(observableOf(groupPaginatedListRD));
fixture.detectChanges();
expect(compAsAny.list$.value).toEqual(groupPaginatedListRD);
expect(comp.getList()).toBeObservable(cold('a', {
@@ -259,6 +259,18 @@ describe('EpersonGroupListComponent test suite', () => {
expect(compAsAny.updateList).toHaveBeenCalled();
});
it('should update list on search triggered', () => {
const options: PaginationComponentOptions = comp.paginationOptions
const event: SearchEvent = {
scope: 'metadata',
query: 'test'
}
spyOn(comp, 'updateList');
comp.onSearch(event);
expect(compAsAny.updateList).toHaveBeenCalledWith(options, 'metadata', 'test');
});
});
});

View File

@@ -16,11 +16,22 @@ import { getDataServiceFor } from '../../../../core/cache/builders/build-decorat
import { EPERSON } from '../../../../core/eperson/models/eperson.resource-type';
import { GROUP } from '../../../../core/eperson/models/group.resource-type';
import { ResourceType } from '../../../../core/shared/resource-type';
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
import { GroupDataService } from '../../../../core/eperson/group-data.service';
import { fadeInOut } from '../../../animations/fade';
export interface SearchEvent {
scope: string;
query: string
}
@Component({
selector: 'ds-eperson-group-list',
styleUrls: ['./eperson-group-list.component.scss'],
templateUrl: './eperson-group-list.component.html'
templateUrl: './eperson-group-list.component.html',
animations: [
fadeInOut
]
})
/**
* Component that shows a list of eperson or group
@@ -43,6 +54,16 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy {
*/
@Output() select: EventEmitter<DSpaceObject> = new EventEmitter<DSpaceObject>();
/**
* Current search query
*/
public currentSearchQuery = '';
/**
* Current search scope
*/
public currentSearchScope = 'metadata';
/**
* Pagination config used to display the list
*/
@@ -52,7 +73,7 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy {
* The data service used to make request.
* It could be EPersonDataService or GroupDataService
*/
private readonly dataService: DataService<DSpaceObject>;
private dataService: DataService<DSpaceObject>;
/**
* A list of eperson or group
@@ -78,18 +99,18 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy {
* @param {Injector} parentInjector
*/
constructor(public dsoNameService: DSONameService, private parentInjector: Injector) {
const resourceType: ResourceType = (this.isListOfEPerson) ? EPERSON : GROUP;
const provider = getDataServiceFor(resourceType);
this.dataService = Injector.create({
providers: [],
parent: this.parentInjector
}).get(provider);
}
/**
* Initialize the component
*/
ngOnInit(): void {
const resourceType: ResourceType = (this.isListOfEPerson) ? EPERSON : GROUP;
const provider = getDataServiceFor(resourceType);
this.dataService = Injector.create({
providers: [],
parent: this.parentInjector
}).get(provider);
this.paginationOptions.id = uniqueId('eperson-group-list-pagination');
this.paginationOptions.pageSize = 5;
@@ -97,7 +118,7 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy {
this.entrySelectedId.next(this.initSelected);
}
this.updateList(this.paginationOptions);
this.updateList(this.paginationOptions, this.currentSearchScope, this.currentSearchQuery);
}
/**
@@ -134,19 +155,33 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy {
*/
onPageChange(page: number): void {
this.paginationOptions.currentPage = page;
this.updateList(this.paginationOptions);
this.updateList(this.paginationOptions, this.currentSearchScope, this.currentSearchQuery);
}
/**
* Method called on search
*/
onSearch(searchEvent: SearchEvent) {
this.currentSearchQuery = searchEvent.query;
this.currentSearchScope = searchEvent.scope;
this.paginationOptions.currentPage = 1;
this.updateList(this.paginationOptions, this.currentSearchScope, this.currentSearchQuery);
}
/**
* Retrieve a paginate list of eperson or group
*/
updateList(config: PaginationComponentOptions): void {
updateList(config: PaginationComponentOptions, scope: string, query: string): void {
const options: FindListOptions = Object.assign({}, new FindListOptions(), {
elementsPerPage: config.pageSize,
currentPage: config.currentPage
});
this.subs.push(this.dataService.findAll(options).pipe(take(1))
const search$: Observable<RemoteData<PaginatedList<DSpaceObject>>> = this.isListOfEPerson ?
(this.dataService as EPersonDataService).searchByScope(scope, query, options) :
(this.dataService as GroupDataService).searchGroups(query, options);
this.subs.push(search$.pipe(take(1))
.subscribe((list: RemoteData<PaginatedList<DSpaceObject>>) => {
this.list$.next(list)
})

View File

@@ -0,0 +1,26 @@
<form class="d-flex justify-content-between"
[formGroup]="searchForm"
(ngSubmit)="submit(searchForm.value); $event.stopImmediatePropagation();" >
<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" 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>
<div>
<button type="button" class="search-button btn btn-secondary" (click)="submit(null); reset()">
{{ labelPrefix + 'button.see-all' | translate }}
</button>
</div>
</form>

View File

@@ -0,0 +1,115 @@
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core';
import { createTestComponent } from '../../../../testing/utils';
import { EpersonSearchBoxComponent } from './eperson-search-box.component';
import { SearchEvent } from '../eperson-group-list.component';
describe('EpersonSearchBoxComponent test suite', () => {
let comp: EpersonSearchBoxComponent;
let compAsAny: any;
let fixture: ComponentFixture<EpersonSearchBoxComponent>;
let de;
let formBuilder: FormBuilder;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
FormsModule,
ReactiveFormsModule,
TranslateModule.forRoot()
],
declarations: [
EpersonSearchBoxComponent,
TestComponent
],
providers: [
FormBuilder,
EpersonSearchBoxComponent
],
schemas: [
NO_ERRORS_SCHEMA
]
}).compileComponents();
}));
describe('', () => {
let testComp: TestComponent;
let testFixture: ComponentFixture<TestComponent>;
// synchronous beforeEach
beforeEach(() => {
const html = `
<ds-group-search-box></ds-group-search-box>`;
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
testComp = testFixture.componentInstance;
});
afterEach(() => {
testFixture.destroy();
});
it('should create EpersonSearchBoxComponent', inject([EpersonSearchBoxComponent], (app: EpersonSearchBoxComponent) => {
expect(app).toBeDefined();
}));
});
describe('', () => {
beforeEach(() => {
// initTestScheduler();
fixture = TestBed.createComponent(EpersonSearchBoxComponent);
formBuilder = TestBed.get(FormBuilder);
comp = fixture.componentInstance;
compAsAny = fixture.componentInstance;
});
afterEach(() => {
comp = null;
compAsAny = null;
de = null;
fixture.destroy();
});
it('should reset the form', () => {
comp.searchForm = formBuilder.group(({
query: 'test',
}));
comp.reset();
expect(comp.searchForm.controls.query.value).toBe('');
});
it('should emit new search event', () => {
const data = {
scope: 'metadata',
query: 'test'
}
const event: SearchEvent = {
scope: 'metadata',
query: 'test'
}
spyOn(comp.search, 'emit');
comp.submit(data);
expect(comp.search.emit).toHaveBeenCalledWith(event);
});
})
});
// declare a test component
@Component({
selector: 'ds-test-cmp',
template: ``
})
class TestComponent {
}

View File

@@ -0,0 +1,65 @@
import { Component, EventEmitter, Output } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { Subscription } from 'rxjs';
import { SearchEvent } from '../eperson-group-list.component';
import { isNotNull } from '../../../../empty.util';
/**
* A component used to show a search box for epersons.
*/
@Component({
selector: 'ds-eperson-search-box',
templateUrl: './eperson-search-box.component.html',
})
export class EpersonSearchBoxComponent {
labelPrefix = 'admin.access-control.epeople.';
/**
* The search form
*/
searchForm;
/**
* List of subscriptions
*/
subs: Subscription[] = [];
/**
* An event fired when a search is triggred.
* Event's payload is a SearchEvent.
*/
@Output() search: EventEmitter<SearchEvent> = new EventEmitter<SearchEvent>();
constructor(private formBuilder: FormBuilder) {
this.searchForm = this.formBuilder.group(({
scope: 'metadata',
query: '',
}));
}
/**
* Reset the search form
*/
reset() {
this.searchForm = this.formBuilder.group(({
scope: 'metadata',
query: '',
}));
}
/**
* Emit a new search event
* @param data Form data
*/
submit(data: any) {
const event: SearchEvent = {
scope: isNotNull(data) ? data.scope : 'metadata',
query: isNotNull(data) ? data.query : ''
}
this.search.emit(event)
}
}

View File

@@ -0,0 +1,20 @@
<form class="d-flex justify-content-between"
[formGroup]="searchForm"
(ngSubmit)="submit(searchForm.value); $event.stopImmediatePropagation();" >
<div class="flex-grow-1 mr-3">
<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>
<div>
<button type="button" class="search-button btn btn-secondary" (click)="submit(null); reset()">
{{ labelPrefix + 'button.see-all' | translate }}
</button>
</div>
</form>

View File

@@ -0,0 +1,114 @@
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core';
import { createTestComponent } from '../../../../testing/utils';
import { GroupSearchBoxComponent } from './group-search-box.component';
import { SearchEvent } from '../eperson-group-list.component';
describe('GroupSearchBoxComponent test suite', () => {
let comp: GroupSearchBoxComponent;
let compAsAny: any;
let fixture: ComponentFixture<GroupSearchBoxComponent>;
let de;
let formBuilder: FormBuilder;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
FormsModule,
ReactiveFormsModule,
TranslateModule.forRoot()
],
declarations: [
GroupSearchBoxComponent,
TestComponent
],
providers: [
FormBuilder,
GroupSearchBoxComponent
],
schemas: [
NO_ERRORS_SCHEMA
]
}).compileComponents();
}));
describe('', () => {
let testComp: TestComponent;
let testFixture: ComponentFixture<TestComponent>;
// synchronous beforeEach
beforeEach(() => {
const html = `
<ds-group-search-box></ds-group-search-box>`;
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
testComp = testFixture.componentInstance;
});
afterEach(() => {
testFixture.destroy();
});
it('should create GroupSearchBoxComponent', inject([GroupSearchBoxComponent], (app: GroupSearchBoxComponent) => {
expect(app).toBeDefined();
}));
});
describe('', () => {
beforeEach(() => {
// initTestScheduler();
fixture = TestBed.createComponent(GroupSearchBoxComponent);
formBuilder = TestBed.get(FormBuilder);
comp = fixture.componentInstance;
compAsAny = fixture.componentInstance;
});
afterEach(() => {
comp = null;
compAsAny = null;
de = null;
fixture.destroy();
});
it('should reset the form', () => {
comp.searchForm = formBuilder.group(({
query: 'test',
}));
comp.reset();
expect(comp.searchForm.controls.query.value).toBe('');
});
it('should emit new search event', () => {
const data = {
query: 'test'
}
const event: SearchEvent = {
scope: '',
query: 'test'
}
spyOn(comp.search, 'emit');
comp.submit(data);
expect(comp.search.emit).toHaveBeenCalledWith(event);
});
})
});
// declare a test component
@Component({
selector: 'ds-test-cmp',
template: ``
})
class TestComponent {
}

View File

@@ -0,0 +1,61 @@
import { Component, EventEmitter, Output } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { Subscription } from 'rxjs';
import { SearchEvent } from '../eperson-group-list.component';
/**
* A component used to show a search box for groups.
*/
@Component({
selector: 'ds-group-search-box',
templateUrl: './group-search-box.component.html',
})
export class GroupSearchBoxComponent {
labelPrefix = 'admin.access-control.groups.';
/**
* The search form
*/
searchForm;
/**
* List of subscriptions
*/
subs: Subscription[] = [];
/**
* An event fired when a search is triggred.
* Event's payload is a SearchEvent.
*/
@Output() search: EventEmitter<SearchEvent> = new EventEmitter<SearchEvent>();
constructor(private formBuilder: FormBuilder) {
this.searchForm = this.formBuilder.group(({
query: '',
}));
}
/**
* Reset the search form
*/
reset() {
this.searchForm = this.formBuilder.group(({
query: '',
}));
}
/**
* Emit a new search event
* @param data Form data
*/
submit(data: any) {
const event: SearchEvent = {
scope: '',
query: data.query
}
this.search.emit(event)
}
}

View File

@@ -198,6 +198,8 @@ import { ResourcePolicyFormComponent } from './resource-policies/form/resource-p
import { EpersonGroupListComponent } from './resource-policies/form/eperson-group-list/eperson-group-list.component';
import { ResourcePolicyTargetResolver } from './resource-policies/resolvers/resource-policy-target.resolver';
import { ResourcePolicyResolver } from './resource-policies/resolvers/resource-policy.resolver';
import { EpersonSearchBoxComponent } from './resource-policies/form/eperson-group-list/eperson-search-box/eperson-search-box.component';
import { GroupSearchBoxComponent } from './resource-policies/form/eperson-group-list/group-search-box/group-search-box.component';
const MODULES = [
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
@@ -378,7 +380,9 @@ const COMPONENTS = [
ItemVersionsNoticeComponent,
ResourcePoliciesComponent,
ResourcePolicyFormComponent,
EpersonGroupListComponent
EpersonGroupListComponent,
EpersonSearchBoxComponent,
GroupSearchBoxComponent
];
const ENTRY_COMPONENTS = [