diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index ada2a1d404..96b4729caa 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -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", diff --git a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.html b/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.html index 729236da93..ce6eccb723 100644 --- a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.html +++ b/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.html @@ -1,4 +1,7 @@ -
+
+ + + { let comp: EpersonGroupListComponent; let compAsAny: any; let fixture: ComponentFixture; 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'); + }); }); }); diff --git a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts b/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts index fd033d8728..02c4726d45 100644 --- a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts +++ b/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts @@ -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 = new EventEmitter(); + /** + * 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; + private dataService: DataService; /** * 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>> = 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>) => { this.list$.next(list) }) diff --git a/src/app/shared/resource-policies/form/eperson-group-list/eperson-search-box/eperson-search-box.component.html b/src/app/shared/resource-policies/form/eperson-group-list/eperson-search-box/eperson-search-box.component.html new file mode 100644 index 0000000000..0d130c723c --- /dev/null +++ b/src/app/shared/resource-policies/form/eperson-group-list/eperson-search-box/eperson-search-box.component.html @@ -0,0 +1,26 @@ +
+
+ +
+
+
+ + + + +
+
+
+ +
+
diff --git a/src/app/shared/resource-policies/form/eperson-group-list/eperson-search-box/eperson-search-box.component.spec.ts b/src/app/shared/resource-policies/form/eperson-group-list/eperson-search-box/eperson-search-box.component.spec.ts new file mode 100644 index 0000000000..5b0456e6a4 --- /dev/null +++ b/src/app/shared/resource-policies/form/eperson-group-list/eperson-search-box/eperson-search-box.component.spec.ts @@ -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; + 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; + + // synchronous beforeEach + beforeEach(() => { + const html = ` + `; + + testFixture = createTestComponent(html, TestComponent) as ComponentFixture; + 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 { + +} diff --git a/src/app/shared/resource-policies/form/eperson-group-list/eperson-search-box/eperson-search-box.component.ts b/src/app/shared/resource-policies/form/eperson-group-list/eperson-search-box/eperson-search-box.component.ts new file mode 100644 index 0000000000..04c5cafd3f --- /dev/null +++ b/src/app/shared/resource-policies/form/eperson-group-list/eperson-search-box/eperson-search-box.component.ts @@ -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 = new EventEmitter(); + + 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) + } +} diff --git a/src/app/shared/resource-policies/form/eperson-group-list/group-search-box/group-search-box.component.html b/src/app/shared/resource-policies/form/eperson-group-list/group-search-box/group-search-box.component.html new file mode 100644 index 0000000000..418996c564 --- /dev/null +++ b/src/app/shared/resource-policies/form/eperson-group-list/group-search-box/group-search-box.component.html @@ -0,0 +1,20 @@ +
+
+
+ + + + +
+
+
+ +
+
diff --git a/src/app/shared/resource-policies/form/eperson-group-list/group-search-box/group-search-box.component.spec.ts b/src/app/shared/resource-policies/form/eperson-group-list/group-search-box/group-search-box.component.spec.ts new file mode 100644 index 0000000000..b23e69c37c --- /dev/null +++ b/src/app/shared/resource-policies/form/eperson-group-list/group-search-box/group-search-box.component.spec.ts @@ -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; + 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; + + // synchronous beforeEach + beforeEach(() => { + const html = ` + `; + + testFixture = createTestComponent(html, TestComponent) as ComponentFixture; + 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 { + +} diff --git a/src/app/shared/resource-policies/form/eperson-group-list/group-search-box/group-search-box.component.ts b/src/app/shared/resource-policies/form/eperson-group-list/group-search-box/group-search-box.component.ts new file mode 100644 index 0000000000..5f4feb582f --- /dev/null +++ b/src/app/shared/resource-policies/form/eperson-group-list/group-search-box/group-search-box.component.ts @@ -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 = new EventEmitter(); + + 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) + } +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 6c59ba9d93..60c2d42717 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -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 = [