mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-14 21:43:04 +00:00
98344: Implemented the Select reviewers advanced step
This commit is contained in:
@@ -1,5 +1,15 @@
|
||||
<div>
|
||||
advancedInfo: {{ (workflowAction$ | async)?.advancedInfo | json }}
|
||||
<p *ngIf="multipleReviewers">{{ 'advanced-workflow-action.select-reviewer.description-multiple' | translate }}</p>
|
||||
<p *ngIf="!multipleReviewers">{{ 'advanced-workflow-action.select-reviewer.description-single' | translate }}</p>
|
||||
|
||||
<ds-reviewers-list *ngIf="groupId !== undefined"
|
||||
[actionConfig]="reviewersListActionConfig"
|
||||
[groupId]="groupId"
|
||||
[ngClass]="groupId ? 'reviewersListWithGroup' : ''"
|
||||
[multipleReviewers]="multipleReviewers"
|
||||
(selectedReviewersUpdated)="selectedReviewers = $event"
|
||||
messagePrefix="advanced-workflow-action-select-reviewer.groups.form.reviewers-list"
|
||||
></ds-reviewers-list>
|
||||
|
||||
<ds-modify-item-overview *ngIf="item$ | async"
|
||||
[item]="item$ | async">
|
||||
|
@@ -0,0 +1,7 @@
|
||||
:host ::ng-deep {
|
||||
.reviewersListWithGroup {
|
||||
#search, #search + form, #search + form + ds-pagination {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,17 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import {
|
||||
rendersAdvancedWorkflowTaskOption
|
||||
} from '../../../shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator';
|
||||
import { AdvancedWorkflowActionComponent } from '../advanced-workflow-action/advanced-workflow-action.component';
|
||||
import { WorkflowAction } from '../../../core/tasks/models/workflow-action-object.model';
|
||||
import {
|
||||
SelectReviewerActionAdvancedInfo
|
||||
} from '../../../core/tasks/models/select-reviewer-action-advanced-info.model';
|
||||
import {
|
||||
EPersonListActionConfig
|
||||
} from '../../../access-control/group-registry/group-form/eperson-list/eperson-list.component';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { EPerson } from '../../../core/eperson/models/eperson.model';
|
||||
|
||||
export const WORKFLOW_ADVANCED_TASK_OPTION_SELECT_REVIEWER = 'submit_select_reviewer';
|
||||
export const ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER = 'selectrevieweraction';
|
||||
@@ -13,7 +22,63 @@ export const ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER = 'selectrevieweraction';
|
||||
templateUrl: './advanced-workflow-action-select-reviewer.component.html',
|
||||
styleUrls: ['./advanced-workflow-action-select-reviewer.component.scss'],
|
||||
})
|
||||
export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkflowActionComponent {
|
||||
export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkflowActionComponent implements OnInit, OnDestroy {
|
||||
|
||||
multipleReviewers = true;
|
||||
|
||||
selectedReviewers: EPerson[];
|
||||
|
||||
reviewersListActionConfig: EPersonListActionConfig;
|
||||
|
||||
/**
|
||||
* When the component is created the value is `undefined`, afterwards it will be set to either the group id or `null`.
|
||||
* It needs to be subscribed in the **ngOnInit()** because otherwise some unnecessary request will be made.
|
||||
*/
|
||||
groupId?: string | null;
|
||||
|
||||
subs: Subscription[] = [];
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subs.forEach((subscription: Subscription) => subscription.unsubscribe());
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit();
|
||||
if (this.multipleReviewers) {
|
||||
this.reviewersListActionConfig = {
|
||||
add: {
|
||||
css: 'btn-outline-primary',
|
||||
disabled: false,
|
||||
icon: 'fas fa-plus',
|
||||
},
|
||||
remove: {
|
||||
css: 'btn-outline-danger',
|
||||
disabled: false,
|
||||
icon: 'fas fa-minus'
|
||||
},
|
||||
};
|
||||
} else {
|
||||
this.reviewersListActionConfig = {
|
||||
add: {
|
||||
css: 'btn-outline-primary',
|
||||
disabled: false,
|
||||
icon: 'fas fa-check',
|
||||
},
|
||||
remove: {
|
||||
css: 'btn-primary',
|
||||
disabled: true,
|
||||
icon: 'fas fa-check'
|
||||
},
|
||||
};
|
||||
}
|
||||
this.subs.push(this.workflowAction$.subscribe((workflowAction: WorkflowAction) => {
|
||||
if (workflowAction) {
|
||||
this.groupId = (workflowAction.advancedInfo as SelectReviewerActionAdvancedInfo[])[0].group;
|
||||
} else {
|
||||
this.groupId = null;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
getType(): string {
|
||||
return ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER;
|
||||
@@ -22,6 +87,7 @@ export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkf
|
||||
createBody(): any {
|
||||
return {
|
||||
[WORKFLOW_ADVANCED_TASK_OPTION_SELECT_REVIEWER]: true,
|
||||
eperson: this.selectedReviewers.map((ePerson: EPerson) => ePerson.id),
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,212 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NO_ERRORS_SCHEMA, SimpleChange } from '@angular/core';
|
||||
import { ComponentFixture, fakeAsync, flush, inject, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { BrowserModule, By } from '@angular/platform-browser';
|
||||
import { Router } from '@angular/router';
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { RestResponse } from '../../../../core/cache/response.models';
|
||||
import { buildPaginatedList, PaginatedList } from '../../../../core/data/paginated-list.model';
|
||||
import { RemoteData } from '../../../../core/data/remote-data';
|
||||
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
|
||||
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
||||
import { EPerson } from '../../../../core/eperson/models/eperson.model';
|
||||
import { Group } from '../../../../core/eperson/models/group.model';
|
||||
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||
import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
|
||||
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||
import { GroupMock, GroupMock2 } from '../../../../shared/testing/group-mock';
|
||||
import { ReviewersListComponent } from './reviewers-list.component';
|
||||
import { EPersonMock, EPersonMock2 } from '../../../../shared/testing/eperson.mock';
|
||||
import {
|
||||
createSuccessfulRemoteDataObject$,
|
||||
createNoContentRemoteDataObject$
|
||||
} from '../../../../shared/remote-data.utils';
|
||||
import { getMockTranslateService } from '../../../../shared/mocks/translate.service.mock';
|
||||
import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder-service.mock';
|
||||
import { TranslateLoaderMock } from '../../../../shared/testing/translate-loader.mock';
|
||||
import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
|
||||
import { RouterMock } from '../../../../shared/mocks/router.mock';
|
||||
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
||||
import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub';
|
||||
|
||||
describe('ReviewersListComponent', () => {
|
||||
let component: ReviewersListComponent;
|
||||
let fixture: ComponentFixture<ReviewersListComponent>;
|
||||
let translateService: TranslateService;
|
||||
let builderService: FormBuilderService;
|
||||
let ePersonDataServiceStub: any;
|
||||
let groupsDataServiceStub: any;
|
||||
let activeGroup;
|
||||
let allEPersons;
|
||||
let allGroups;
|
||||
let epersonMembers;
|
||||
let subgroupMembers;
|
||||
let paginationService;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
activeGroup = GroupMock;
|
||||
epersonMembers = [EPersonMock2];
|
||||
subgroupMembers = [GroupMock2];
|
||||
allEPersons = [EPersonMock, EPersonMock2];
|
||||
allGroups = [GroupMock, GroupMock2];
|
||||
ePersonDataServiceStub = {
|
||||
activeGroup: activeGroup,
|
||||
epersonMembers: epersonMembers,
|
||||
subgroupMembers: subgroupMembers,
|
||||
findAllByHref(href: string): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||
return createSuccessfulRemoteDataObject$(buildPaginatedList<EPerson>(new PageInfo(), groupsDataServiceStub.getEPersonMembers()));
|
||||
},
|
||||
searchByScope(scope: string, query: string): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||
if (query === '') {
|
||||
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), allEPersons));
|
||||
}
|
||||
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []));
|
||||
},
|
||||
clearEPersonRequests() {
|
||||
// empty
|
||||
},
|
||||
clearLinkRequests() {
|
||||
// empty
|
||||
},
|
||||
getEPeoplePageRouterLink(): string {
|
||||
return '/access-control/epeople';
|
||||
}
|
||||
};
|
||||
groupsDataServiceStub = {
|
||||
activeGroup: activeGroup,
|
||||
epersonMembers: epersonMembers,
|
||||
subgroupMembers: subgroupMembers,
|
||||
allGroups: allGroups,
|
||||
getActiveGroup(): Observable<Group> {
|
||||
return observableOf(activeGroup);
|
||||
},
|
||||
getEPersonMembers() {
|
||||
return this.epersonMembers;
|
||||
},
|
||||
searchGroups(query: string): Observable<RemoteData<PaginatedList<Group>>> {
|
||||
if (query === '') {
|
||||
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), this.allGroups));
|
||||
}
|
||||
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []));
|
||||
},
|
||||
addMemberToGroup(parentGroup, eperson: EPerson): Observable<RestResponse> {
|
||||
this.epersonMembers = [...this.epersonMembers, eperson];
|
||||
return observableOf(new RestResponse(true, 200, 'Success'));
|
||||
},
|
||||
clearGroupsRequests() {
|
||||
// empty
|
||||
},
|
||||
clearGroupLinkRequests() {
|
||||
// empty
|
||||
},
|
||||
getGroupEditPageRouterLink(group: Group): string {
|
||||
return '/access-control/groups/' + group.id;
|
||||
},
|
||||
deleteMemberFromGroup(parentGroup, epersonToDelete: EPerson): Observable<RestResponse> {
|
||||
this.epersonMembers = this.epersonMembers.find((eperson: EPerson) => {
|
||||
if (eperson.id !== epersonToDelete.id) {
|
||||
return eperson;
|
||||
}
|
||||
});
|
||||
if (this.epersonMembers === undefined) {
|
||||
this.epersonMembers = [];
|
||||
}
|
||||
return observableOf(new RestResponse(true, 200, 'Success'));
|
||||
},
|
||||
findById(id: string) {
|
||||
for (const group of allGroups) {
|
||||
if (group.id === id) {
|
||||
console.log('found', group);
|
||||
return createSuccessfulRemoteDataObject$(group);
|
||||
}
|
||||
}
|
||||
return createNoContentRemoteDataObject$();
|
||||
},
|
||||
editGroup() {
|
||||
// empty
|
||||
}
|
||||
};
|
||||
builderService = getMockFormBuilderService();
|
||||
translateService = getMockTranslateService();
|
||||
|
||||
paginationService = new PaginationServiceStub();
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: TranslateLoaderMock
|
||||
}
|
||||
}),
|
||||
],
|
||||
declarations: [ReviewersListComponent],
|
||||
providers: [ReviewersListComponent,
|
||||
{ provide: EPersonDataService, useValue: ePersonDataServiceStub },
|
||||
{ provide: GroupDataService, useValue: groupsDataServiceStub },
|
||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
||||
{ provide: FormBuilderService, useValue: builderService },
|
||||
{ provide: Router, useValue: new RouterMock() },
|
||||
{ provide: PaginationService, useValue: paginationService },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ReviewersListComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
afterEach(fakeAsync(() => {
|
||||
fixture.destroy();
|
||||
flush();
|
||||
component = null;
|
||||
fixture.debugElement.nativeElement.remove();
|
||||
}));
|
||||
|
||||
it('should create ReviewersListComponent', inject([ReviewersListComponent], (comp: ReviewersListComponent) => {
|
||||
expect(comp).toBeDefined();
|
||||
}));
|
||||
|
||||
describe('when no group is selected', () => {
|
||||
beforeEach(() => {
|
||||
component.ngOnChanges({
|
||||
groupId: new SimpleChange(undefined, null, true)
|
||||
});
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show no epersons because no group is selected', () => {
|
||||
const epersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child'));
|
||||
expect(epersonIdsFound.length).toEqual(0);
|
||||
epersonMembers.map((eperson: EPerson) => {
|
||||
expect(epersonIdsFound.find((foundEl) => {
|
||||
return (foundEl.nativeElement.textContent.trim() === eperson.uuid);
|
||||
})).not.toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when group is selected', () => {
|
||||
beforeEach(() => {
|
||||
component.ngOnChanges({
|
||||
groupId: new SimpleChange(undefined, GroupMock.id, true)
|
||||
});
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show all eperson members of group', () => {
|
||||
const epersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child'));
|
||||
expect(epersonIdsFound.length).toEqual(1);
|
||||
epersonMembers.map((eperson: EPerson) => {
|
||||
expect(epersonIdsFound.find((foundEl) => {
|
||||
return (foundEl.nativeElement.textContent.trim() === eperson.uuid);
|
||||
})).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,124 @@
|
||||
import { Component, OnDestroy, OnInit, Input, OnChanges, SimpleChanges, EventEmitter, Output } from '@angular/core';
|
||||
import { FormBuilder } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
|
||||
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
||||
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
||||
import { Group } from '../../../../core/eperson/models/group.model';
|
||||
import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators';
|
||||
import {
|
||||
EPersonListComponent,
|
||||
EPersonListActionConfig
|
||||
} from '../../../../access-control/group-registry/group-form/eperson-list/eperson-list.component';
|
||||
import { EpersonDtoModel } from '../../../../core/eperson/models/eperson-dto.model';
|
||||
import { EPerson } from '../../../../core/eperson/models/eperson.model';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { hasValue } from '../../../../shared/empty.util';
|
||||
import { PaginatedList } from '../../../../core/data/paginated-list.model';
|
||||
|
||||
/**
|
||||
* Keys to keep track of specific subscriptions
|
||||
*/
|
||||
enum SubKey {
|
||||
ActiveGroup,
|
||||
MembersDTO,
|
||||
SearchResultsDTO,
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'ds-reviewers-list',
|
||||
// templateUrl: './reviewers-list.component.html',
|
||||
templateUrl: '../../../../access-control/group-registry/group-form/eperson-list/eperson-list.component.html',
|
||||
})
|
||||
export class ReviewersListComponent extends EPersonListComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
@Input()
|
||||
groupId: string | null;
|
||||
|
||||
@Input()
|
||||
actionConfig: EPersonListActionConfig;
|
||||
|
||||
@Input()
|
||||
multipleReviewers: boolean;
|
||||
|
||||
@Output()
|
||||
selectedReviewersUpdated: EventEmitter<EPerson[]> = new EventEmitter();
|
||||
|
||||
selectedReviewers: EpersonDtoModel[] = [];
|
||||
|
||||
constructor(protected groupService: GroupDataService,
|
||||
public ePersonDataService: EPersonDataService,
|
||||
translateService: TranslateService,
|
||||
notificationsService: NotificationsService,
|
||||
formBuilder: FormBuilder,
|
||||
paginationService: PaginationService,
|
||||
router: Router) {
|
||||
super(groupService, ePersonDataService, translateService, notificationsService, formBuilder, paginationService, router);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.searchForm = this.formBuilder.group(({
|
||||
scope: 'metadata',
|
||||
query: '',
|
||||
}));
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
this.groupId = changes.groupId.currentValue;
|
||||
if (changes.groupId.currentValue !== changes.groupId.previousValue) {
|
||||
if (this.groupId === null) {
|
||||
this.retrieveMembers(this.config.currentPage);
|
||||
} else {
|
||||
this.subs.set(SubKey.ActiveGroup, this.groupService.findById(this.groupId).pipe(
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
).subscribe((activeGroup: Group) => {
|
||||
if (activeGroup != null) {
|
||||
this.groupDataService.editGroup(activeGroup);
|
||||
this.groupBeingEdited = activeGroup;
|
||||
this.retrieveMembers(this.config.currentPage);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
retrieveMembers(page: number): void {
|
||||
this.config.currentPage = page;
|
||||
if (this.groupId === null) {
|
||||
this.unsubFrom(SubKey.MembersDTO);
|
||||
const paginatedListOfDTOs: PaginatedList<EpersonDtoModel> = new PaginatedList();
|
||||
paginatedListOfDTOs.page = this.selectedReviewers;
|
||||
this.ePeopleMembersOfGroupDtos.next(paginatedListOfDTOs);
|
||||
} else {
|
||||
super.retrieveMembers(page);
|
||||
}
|
||||
}
|
||||
|
||||
isMemberOfGroup(possibleMember: EPerson): Observable<boolean> {
|
||||
return observableOf(hasValue(this.selectedReviewers.find((reviewer: EpersonDtoModel) => reviewer.eperson.id === possibleMember.id)));
|
||||
}
|
||||
|
||||
deleteMemberFromGroup(ePerson: EpersonDtoModel) {
|
||||
ePerson.memberOfGroup = false;
|
||||
const index = this.selectedReviewers.indexOf(ePerson);
|
||||
if (index !== -1) {
|
||||
this.selectedReviewers.splice(index, 1);
|
||||
}
|
||||
this.selectedReviewersUpdated.emit(this.selectedReviewers.map((epersonDtoModel: EpersonDtoModel) => epersonDtoModel.eperson));
|
||||
}
|
||||
|
||||
addMemberToGroup(ePerson: EpersonDtoModel) {
|
||||
ePerson.memberOfGroup = true;
|
||||
if (!this.multipleReviewers) {
|
||||
for (const selectedReviewer of this.selectedReviewers) {
|
||||
selectedReviewer.memberOfGroup = false;
|
||||
}
|
||||
this.selectedReviewers = [];
|
||||
}
|
||||
this.selectedReviewers.push(ePerson);
|
||||
this.selectedReviewersUpdated.emit(this.selectedReviewers.map((epersonDtoModel: EpersonDtoModel) => epersonDtoModel.eperson));
|
||||
}
|
||||
|
||||
}
|
@@ -18,16 +18,21 @@ import { AdvancedWorkflowActionPageComponent } from './advanced-workflow-action/
|
||||
import {
|
||||
AdvancedClaimedTaskActionsDirective
|
||||
} from './advanced-workflow-action/advanced-workflow-actions-loader/advanced-claimed-task-actions.directive';
|
||||
import { AccessControlModule } from '../access-control/access-control.module';
|
||||
import {
|
||||
ReviewersListComponent
|
||||
} from './advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
WorkflowItemsEditPageRoutingModule,
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
SubmissionModule,
|
||||
StatisticsModule,
|
||||
ItemPageModule
|
||||
],
|
||||
imports: [
|
||||
WorkflowItemsEditPageRoutingModule,
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
SubmissionModule,
|
||||
StatisticsModule,
|
||||
ItemPageModule,
|
||||
AccessControlModule
|
||||
],
|
||||
declarations: [
|
||||
WorkflowItemDeleteComponent,
|
||||
ThemedWorkflowItemDeleteComponent,
|
||||
@@ -38,6 +43,7 @@ import {
|
||||
AdvancedWorkflowActionSelectReviewerComponent,
|
||||
AdvancedWorkflowActionPageComponent,
|
||||
AdvancedClaimedTaskActionsDirective,
|
||||
ReviewersListComponent,
|
||||
]
|
||||
})
|
||||
/**
|
||||
|
Reference in New Issue
Block a user