Merge branch 'w2p-98211_advanced-workflow-actions-7.2' into w2p-98211_advanced-workflow-actions-main

# Conflicts:
#	src/app/core/core.module.ts
#	src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.spec.ts
This commit is contained in:
Alexandre Vryghem
2023-01-18 13:46:38 +01:00
28 changed files with 471 additions and 169 deletions

View File

@@ -157,6 +157,9 @@ import { SequenceService } from './shared/sequence.service';
import { CoreState } from './core-state.model'; import { CoreState } from './core-state.model';
import { GroupDataService } from './eperson/group-data.service'; import { GroupDataService } from './eperson/group-data.service';
import { SubmissionAccessesModel } from './config/models/config-submission-accesses.model'; import { SubmissionAccessesModel } from './config/models/config-submission-accesses.model';
import { RatingAdvancedWorkflowInfo } from './tasks/models/rating-advanced-workflow-info.model';
import { AdvancedWorkflowInfo } from './tasks/models/advanced-workflow-info.model';
import { SelectReviewerAdvancedWorkflowInfo } from './tasks/models/select-reviewer-advanced-workflow-info.model';
import { AccessStatusObject } from '../shared/object-list/access-status-badge/access-status.model'; import { AccessStatusObject } from '../shared/object-list/access-status-badge/access-status.model';
import { AccessStatusDataService } from './data/access-status-data.service'; import { AccessStatusDataService } from './data/access-status-data.service';
import { LinkHeadService } from './services/link-head.service'; import { LinkHeadService } from './services/link-head.service';
@@ -170,9 +173,6 @@ import { OrcidHistory } from './orcid/model/orcid-history.model';
import { OrcidAuthService } from './orcid/orcid-auth.service'; import { OrcidAuthService } from './orcid/orcid-auth.service';
import { VocabularyDataService } from './submission/vocabularies/vocabulary.data.service'; import { VocabularyDataService } from './submission/vocabularies/vocabulary.data.service';
import { VocabularyEntryDetailsDataService } from './submission/vocabularies/vocabulary-entry-details.data.service'; import { VocabularyEntryDetailsDataService } from './submission/vocabularies/vocabulary-entry-details.data.service';
import { RatingReviewerActionAdvancedInfo } from './tasks/models/rating-reviewer-action-advanced-info.model';
import { ReviewerActionAdvancedInfo } from './tasks/models/reviewer-action-advanced-info.model';
import { SelectReviewerActionAdvancedInfo } from './tasks/models/select-reviewer-action-advanced-info.model';
/** /**
* When not in production, endpoint responses can be mocked for testing purposes * When not in production, endpoint responses can be mocked for testing purposes
@@ -341,9 +341,9 @@ export const models =
Version, Version,
VersionHistory, VersionHistory,
WorkflowAction, WorkflowAction,
ReviewerActionAdvancedInfo, AdvancedWorkflowInfo,
RatingReviewerActionAdvancedInfo, RatingAdvancedWorkflowInfo,
SelectReviewerActionAdvancedInfo, SelectReviewerAdvancedWorkflowInfo,
TemplateItem, TemplateItem,
Feature, Feature,
Authorization, Authorization,

View File

@@ -0,0 +1,11 @@
import { autoserialize } from 'cerialize';
/**
* An abstract model class for a {@link AdvancedWorkflowInfo}
*/
export abstract class AdvancedWorkflowInfo {
@autoserialize
id: string;
}

View File

@@ -0,0 +1,17 @@
import { ResourceType } from '../../shared/resource-type';
/**
* The resource type for {@link RatingAdvancedWorkflowInfo}
*
* Needs to be in a separate file to prevent circular
* dependencies in webpack.
*/
export const RATING_ADVANCED_WORKFLOW_INFO = new ResourceType('ratingrevieweraction');
/**
* The resource type for {@link SelectReviewerAdvancedWorkflowInfo}
*
* Needs to be in a separate file to prevent circular
* dependencies in webpack.
*/
export const SELECT_REVIEWER_ADVANCED_WORKFLOW_INFO = new ResourceType('selectrevieweraction');

View File

@@ -0,0 +1,28 @@
import { typedObject } from '../../cache/builders/build-decorators';
import { inheritSerialization, autoserialize } from 'cerialize';
import { RATING_ADVANCED_WORKFLOW_INFO } from './advanced-workflow-info.resource-type';
import { AdvancedWorkflowInfo } from './advanced-workflow-info.model';
import { ResourceType } from '../../shared/resource-type';
/**
* A model class for a {@link RatingAdvancedWorkflowInfo}
*/
@typedObject
@inheritSerialization(AdvancedWorkflowInfo)
export class RatingAdvancedWorkflowInfo extends AdvancedWorkflowInfo {
static type: ResourceType = RATING_ADVANCED_WORKFLOW_INFO;
/**
* Whether the description is required.
*/
@autoserialize
descriptionRequired: boolean;
/**
* The maximum value.
*/
@autoserialize
maxValue: number;
}

View File

@@ -1,27 +0,0 @@
import { typedObject } from '../../cache/builders/build-decorators';
import { inheritSerialization, autoserialize } from 'cerialize';
import { RATING_REVIEWER_ACTION_ADVANCED_INFO } from './reviewer-action-advanced-info.resource-type';
import { ReviewerActionAdvancedInfo } from './reviewer-action-advanced-info.model';
/**
* A model class for a {@link RatingReviewerActionAdvancedInfo}
*/
@typedObject
@inheritSerialization(ReviewerActionAdvancedInfo)
export class RatingReviewerActionAdvancedInfo extends ReviewerActionAdvancedInfo {
static type = RATING_REVIEWER_ACTION_ADVANCED_INFO;
/**
* Whether the description is required.
*/
@autoserialize
descriptionRequired: boolean;
/**
* The maximum value.
*/
@autoserialize
maxValue: number;
}

View File

@@ -1,16 +0,0 @@
import { typedObject } from '../../cache/builders/build-decorators';
import { autoserialize } from 'cerialize';
import { REVIEWER_ACTION_ADVANCED_INFO } from './reviewer-action-advanced-info.resource-type';
/**
* A model class for a {@link ReviewerActionAdvancedInfo}
*/
@typedObject
export class ReviewerActionAdvancedInfo {
static type = REVIEWER_ACTION_ADVANCED_INFO;
@autoserialize
id: string;
}

View File

@@ -1,25 +0,0 @@
import { ResourceType } from '../../shared/resource-type';
/**
* The resource type for {@link ReviewerActionAdvancedInfo}
*
* Needs to be in a separate file to prevent circular
* dependencies in webpack.
*/
export const REVIEWER_ACTION_ADVANCED_INFO = new ResourceType('revieweraction');
/**
* The resource type for {@link RatingReviewerActionAdvancedInfo}
*
* Needs to be in a separate file to prevent circular
* dependencies in webpack.
*/
export const RATING_REVIEWER_ACTION_ADVANCED_INFO = new ResourceType('ratingrevieweraction');
/**
* The resource type for {@link SelectReviewerActionAdvancedInfo}
*
* Needs to be in a separate file to prevent circular
* dependencies in webpack.
*/
export const SELECT_REVIEWER_ACTION_ADVANCED_INFO = new ResourceType('selectrevieweraction');

View File

@@ -1,18 +0,0 @@
import { typedObject } from '../../cache/builders/build-decorators';
import { inheritSerialization, autoserialize } from 'cerialize';
import { SELECT_REVIEWER_ACTION_ADVANCED_INFO } from './reviewer-action-advanced-info.resource-type';
import { ReviewerActionAdvancedInfo } from './reviewer-action-advanced-info.model';
/**
* A model class for a {@link SelectReviewerActionAdvancedInfo}
*/
@typedObject
@inheritSerialization(ReviewerActionAdvancedInfo)
export class SelectReviewerActionAdvancedInfo extends ReviewerActionAdvancedInfo {
static type = SELECT_REVIEWER_ACTION_ADVANCED_INFO;
@autoserialize
group: string;
}

View File

@@ -0,0 +1,19 @@
import { typedObject } from '../../cache/builders/build-decorators';
import { inheritSerialization, autoserialize } from 'cerialize';
import { SELECT_REVIEWER_ADVANCED_WORKFLOW_INFO } from './advanced-workflow-info.resource-type';
import { AdvancedWorkflowInfo } from './advanced-workflow-info.model';
import { ResourceType } from '../../shared/resource-type';
/**
* A model class for a {@link SelectReviewerAdvancedWorkflowInfo}
*/
@typedObject
@inheritSerialization(AdvancedWorkflowInfo)
export class SelectReviewerAdvancedWorkflowInfo extends AdvancedWorkflowInfo {
static type: ResourceType = SELECT_REVIEWER_ADVANCED_WORKFLOW_INFO;
@autoserialize
group: string;
}

View File

@@ -2,7 +2,7 @@ import { inheritSerialization, autoserialize } from 'cerialize';
import { typedObject } from '../../cache/builders/build-decorators'; import { typedObject } from '../../cache/builders/build-decorators';
import { DSpaceObject } from '../../shared/dspace-object.model'; import { DSpaceObject } from '../../shared/dspace-object.model';
import { WORKFLOW_ACTION } from './workflow-action-object.resource-type'; import { WORKFLOW_ACTION } from './workflow-action-object.resource-type';
import { ReviewerActionAdvancedInfo } from './reviewer-action-advanced-info.model'; import { AdvancedWorkflowInfo } from './advanced-workflow-info.model';
/** /**
* A model class for a WorkflowAction * A model class for a WorkflowAction
@@ -40,6 +40,6 @@ export class WorkflowAction extends DSpaceObject {
* The advanced info required by the advanced options * The advanced info required by the advanced options
*/ */
@autoserialize @autoserialize
advancedInfo: ReviewerActionAdvancedInfo[]; advancedInfo: AdvancedWorkflowInfo[];
} }

View File

@@ -7,9 +7,8 @@ import { getAdvancedWorkflowRoute } from '../../../../workflowitems-edit-page/wo
/** /**
* Abstract component for rendering an advanced claimed task's action * Abstract component for rendering an advanced claimed task's action
* To create a child-component for a new option: * To create a child-component for a new option:
* - Set the "option" of the component * - Set the "option" and "workflowType" of the component
* - Add a @rendersWorkflowTaskOption annotation to your component providing the same enum value * - Add a @rendersWorkflowTaskOption annotation to your component providing the same enum value
* - Optionally overwrite createBody if the request body requires more than just the option
*/ */
@Component({ @Component({
selector: 'ds-advanced-claimed-task-action-abstract', selector: 'ds-advanced-claimed-task-action-abstract',
@@ -17,7 +16,10 @@ import { getAdvancedWorkflowRoute } from '../../../../workflowitems-edit-page/wo
}) })
export abstract class AdvancedClaimedTaskActionsAbstractComponent extends ClaimedTaskActionsAbstractComponent implements OnInit { export abstract class AdvancedClaimedTaskActionsAbstractComponent extends ClaimedTaskActionsAbstractComponent implements OnInit {
workflowType: string; /**
* The {@link WorkflowAction} id of the advanced workflow that needs to be opened.
*/
abstract workflowType: string;
/** /**
* Route to the workflow's task page * Route to the workflow's task page
@@ -40,6 +42,9 @@ export abstract class AdvancedClaimedTaskActionsAbstractComponent extends Claime
})); }));
} }
/**
* Navigates to the advanced workflow page based on the {@link workflow}.
*/
openAdvancedClaimedTaskTab(): void { openAdvancedClaimedTaskTab(): void {
void this.router.navigate([this.workflowTaskPageRoute], { void this.router.navigate([this.workflowTaskPageRoute], {
queryParams: { queryParams: {

View File

@@ -9,11 +9,14 @@ import {
} from '../abstract/advanced-claimed-task-actions-abstract.component'; } from '../abstract/advanced-claimed-task-actions-abstract.component';
import { import {
ADVANCED_WORKFLOW_ACTION_RATING, ADVANCED_WORKFLOW_ACTION_RATING,
WORKFLOW_ADVANCED_TASK_OPTION_RATING, ADVANCED_WORKFLOW_TASK_OPTION_RATING,
} from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component'; } from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component';
import { rendersWorkflowTaskOption } from '../switcher/claimed-task-actions-decorator'; import { rendersWorkflowTaskOption } from '../switcher/claimed-task-actions-decorator';
@rendersWorkflowTaskOption(WORKFLOW_ADVANCED_TASK_OPTION_RATING) /**
* Advanced Workflow button that redirect to the {@link AdvancedWorkflowActionRatingComponent}
*/
@rendersWorkflowTaskOption(ADVANCED_WORKFLOW_TASK_OPTION_RATING)
@Component({ @Component({
selector: 'ds-advanced-claimed-task-action-rating-reviewer', selector: 'ds-advanced-claimed-task-action-rating-reviewer',
templateUrl: './advanced-claimed-task-action-rating.component.html', templateUrl: './advanced-claimed-task-action-rating.component.html',
@@ -24,7 +27,7 @@ export class AdvancedClaimedTaskActionRatingComponent extends AdvancedClaimedTas
/** /**
* This component represents the advanced select option * This component represents the advanced select option
*/ */
option = WORKFLOW_ADVANCED_TASK_OPTION_RATING; option = ADVANCED_WORKFLOW_TASK_OPTION_RATING;
workflowType = ADVANCED_WORKFLOW_ACTION_RATING; workflowType = ADVANCED_WORKFLOW_ACTION_RATING;

View File

@@ -10,10 +10,13 @@ import { SearchService } from '../../../../core/shared/search/search.service';
import { RequestService } from '../../../../core/data/request.service'; import { RequestService } from '../../../../core/data/request.service';
import { import {
ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER, ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER,
WORKFLOW_ADVANCED_TASK_OPTION_SELECT_REVIEWER ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER
} from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component'; } from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component';
@rendersWorkflowTaskOption(WORKFLOW_ADVANCED_TASK_OPTION_SELECT_REVIEWER) /**
* Advanced Workflow button that redirect to the {@link AdvancedWorkflowActionSelectReviewerComponent}
*/
@rendersWorkflowTaskOption(ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER)
@Component({ @Component({
selector: 'ds-advanced-claimed-task-action-select-reviewer', selector: 'ds-advanced-claimed-task-action-select-reviewer',
templateUrl: './advanced-claimed-task-action-select-reviewer.component.html', templateUrl: './advanced-claimed-task-action-select-reviewer.component.html',
@@ -24,7 +27,7 @@ export class AdvancedClaimedTaskActionSelectReviewerComponent extends AdvancedCl
/** /**
* This component represents the advanced select option * This component represents the advanced select option
*/ */
option = WORKFLOW_ADVANCED_TASK_OPTION_SELECT_REVIEWER; option = ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER;
workflowType = ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER; workflowType = ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER;

View File

@@ -1,6 +1,6 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AdvancedWorkflowActionPageComponent } from './advanced-workflow-action-page.component'; import { AdvancedWorkflowActionPageComponent } from './advanced-workflow-action-page.component';
import { ActivatedRoute, convertToParamMap } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
describe('AdvancedWorkflowActionPageComponent', () => { describe('AdvancedWorkflowActionPageComponent', () => {
@@ -20,9 +20,9 @@ describe('AdvancedWorkflowActionPageComponent', () => {
provide: ActivatedRoute, provide: ActivatedRoute,
useValue: { useValue: {
snapshot: { snapshot: {
queryParams: convertToParamMap({ queryParams: {
workflow: 'testaction', workflow: 'testaction',
}), },
}, },
}, },
}, },

View File

@@ -1,6 +1,10 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
/**
* The Advanced Workflow page containing the correct {@link AdvancedWorkflowActionComponent}
* based on the route parameters.
*/
@Component({ @Component({
selector: 'ds-advanced-workflow-action-page', selector: 'ds-advanced-workflow-action-page',
templateUrl: './advanced-workflow-action-page.component.html', templateUrl: './advanced-workflow-action-page.component.html',

View File

@@ -1,6 +1,9 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AdvancedWorkflowActionRatingComponent } from './advanced-workflow-action-rating.component'; import {
import { ActivatedRoute, convertToParamMap, Router } from '@angular/router'; AdvancedWorkflowActionRatingComponent,
ADVANCED_WORKFLOW_TASK_OPTION_RATING
} from './advanced-workflow-action-rating.component';
import { ActivatedRoute, Router } from '@angular/router';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service'; import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service';
import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service';
@@ -17,10 +20,19 @@ import { TranslateModule } from '@ngx-translate/core';
import { VarDirective } from '../../../shared/utils/var.directive'; import { VarDirective } from '../../../shared/utils/var.directive';
import { RatingModule } from 'ngx-bootstrap/rating'; import { RatingModule } from 'ngx-bootstrap/rating';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { WorkflowItem } from '../../../core/submission/models/workflowitem.model';
import { createSuccessfulRemoteDataObject$, createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils';
import { Item } from '../../../core/shared/item.model';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response';
import { RatingAdvancedWorkflowInfo } from '../../../core/tasks/models/rating-advanced-workflow-info.model';
const claimedTaskId = '2';
const workflowId = '1'; const workflowId = '1';
describe('AdvancedWorkflowActionRatingComponent', () => { describe('AdvancedWorkflowActionRatingComponent', () => {
const workflowItem: WorkflowItem = new WorkflowItem();
workflowItem.item = createSuccessfulRemoteDataObject$(new Item());
let component: AdvancedWorkflowActionRatingComponent; let component: AdvancedWorkflowActionRatingComponent;
let fixture: ComponentFixture<AdvancedWorkflowActionRatingComponent>; let fixture: ComponentFixture<AdvancedWorkflowActionRatingComponent>;
@@ -52,11 +64,13 @@ describe('AdvancedWorkflowActionRatingComponent', () => {
useValue: { useValue: {
data: observableOf({ data: observableOf({
id: workflowId, id: workflowId,
wfi: createSuccessfulRemoteDataObject(workflowItem),
}), }),
snapshot: { snapshot: {
queryParams: convertToParamMap({ queryParams: {
claimedTask: claimedTaskId,
workflow: 'testaction', workflow: 'testaction',
}), },
}, },
}, },
}, },
@@ -67,6 +81,7 @@ describe('AdvancedWorkflowActionRatingComponent', () => {
{ provide: WorkflowActionDataService, useValue: workflowActionDataService }, { provide: WorkflowActionDataService, useValue: workflowActionDataService },
{ provide: WorkflowItemDataService, useValue: workflowItemDataService }, { provide: WorkflowItemDataService, useValue: workflowItemDataService },
], ],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents(); }).compileComponents();
}); });
@@ -80,7 +95,96 @@ describe('AdvancedWorkflowActionRatingComponent', () => {
fixture.debugElement.nativeElement.remove(); fixture.debugElement.nativeElement.remove();
}); });
it('should create', () => { describe('performAction', () => {
expect(component).toBeTruthy(); let ratingAdvancedWorkflowInfo: RatingAdvancedWorkflowInfo;
beforeEach(() => {
ratingAdvancedWorkflowInfo = new RatingAdvancedWorkflowInfo();
ratingAdvancedWorkflowInfo.maxValue = 5;
spyOn(component, 'getAdvancedInfo').and.returnValue(ratingAdvancedWorkflowInfo);
spyOn(component, 'previousPage');
// The form validators are set in the HTML code so the getAdvancedInfo needs to return a value
fixture.detectChanges();
});
describe('with required review', () => {
beforeEach(() => {
ratingAdvancedWorkflowInfo.descriptionRequired = true;
fixture.detectChanges();
});
it('should call the claimedTaskDataService with the rating and the required description when it has been rated and return to the mydspace page', () => {
spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true)));
component.ratingForm.setValue({
review: 'Good job!',
rating: 4,
});
component.performAction();
expect(claimedTaskDataService.submitTask).toHaveBeenCalledWith(claimedTaskId, {
[ADVANCED_WORKFLOW_TASK_OPTION_RATING]: true,
review: 'Good job!',
score: 4,
});
expect(notificationService.success).toHaveBeenCalled();
expect(component.previousPage).toHaveBeenCalled();
});
it('should not call the claimedTaskDataService when the required description is empty', () => {
spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true)));
component.ratingForm.setValue({
review: '',
rating: 4,
});
component.performAction();
expect(claimedTaskDataService.submitTask).not.toHaveBeenCalled();
expect(notificationService.success).not.toHaveBeenCalled();
expect(component.previousPage).not.toHaveBeenCalled();
});
});
describe('with an optional review', () => {
beforeEach(() => {
ratingAdvancedWorkflowInfo.descriptionRequired = false;
fixture.detectChanges();
});
it('should call the claimedTaskDataService with the optional review when provided and return to the mydspace page', () => {
spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true)));
component.ratingForm.setValue({
review: 'Good job!',
rating: 4,
});
component.performAction();
expect(claimedTaskDataService.submitTask).toHaveBeenCalledWith(claimedTaskId, {
[ADVANCED_WORKFLOW_TASK_OPTION_RATING]: true,
review: 'Good job!',
score: 4,
});
expect(notificationService.success).toHaveBeenCalled();
expect(component.previousPage).toHaveBeenCalled();
});
it('should call the claimedTaskDataService when the optional description is empty and return to the mydspace page', () => {
spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true)));
component.ratingForm.setValue({
review: '',
rating: 4,
});
component.performAction();
expect(claimedTaskDataService.submitTask).toHaveBeenCalledWith(claimedTaskId, {
[ADVANCED_WORKFLOW_TASK_OPTION_RATING]: true,
score: 4,
});
expect(notificationService.success).toHaveBeenCalled();
expect(component.previousPage).toHaveBeenCalled();
});
});
}); });
}); });

View File

@@ -6,12 +6,15 @@ import { AdvancedWorkflowActionComponent } from '../advanced-workflow-action/adv
import { FormGroup, FormControl } from '@angular/forms'; import { FormGroup, FormControl } from '@angular/forms';
import { WorkflowAction } from '../../../core/tasks/models/workflow-action-object.model'; import { WorkflowAction } from '../../../core/tasks/models/workflow-action-object.model';
import { import {
RatingReviewerActionAdvancedInfo RatingAdvancedWorkflowInfo
} from '../../../core/tasks/models/rating-reviewer-action-advanced-info.model'; } from '../../../core/tasks/models/rating-advanced-workflow-info.model';
export const WORKFLOW_ADVANCED_TASK_OPTION_RATING = 'submit_score'; export const ADVANCED_WORKFLOW_TASK_OPTION_RATING = 'submit_score';
export const ADVANCED_WORKFLOW_ACTION_RATING = 'scorereviewaction'; export const ADVANCED_WORKFLOW_ACTION_RATING = 'scorereviewaction';
/**
* The page on which reviewers can rate submitted items.
*/
@rendersAdvancedWorkflowTaskOption(ADVANCED_WORKFLOW_ACTION_RATING) @rendersAdvancedWorkflowTaskOption(ADVANCED_WORKFLOW_ACTION_RATING)
@Component({ @Component({
selector: 'ds-advanced-workflow-action-rating-reviewer', selector: 'ds-advanced-workflow-action-rating-reviewer',
@@ -23,7 +26,7 @@ export class AdvancedWorkflowActionRatingComponent extends AdvancedWorkflowActio
ratingForm: FormGroup; ratingForm: FormGroup;
ngOnInit() { ngOnInit(): void {
super.ngOnInit(); super.ngOnInit();
this.ratingForm = new FormGroup({ this.ratingForm = new FormGroup({
review: new FormControl(''), review: new FormControl(''),
@@ -43,9 +46,12 @@ export class AdvancedWorkflowActionRatingComponent extends AdvancedWorkflowActio
} }
} }
/**
* Returns the task option, the score and the review if one was provided
*/
createBody(): any { createBody(): any {
const body = { const body = {
[WORKFLOW_ADVANCED_TASK_OPTION_RATING]: true, [ADVANCED_WORKFLOW_TASK_OPTION_RATING]: true,
score: this.ratingForm.get('rating').value, score: this.ratingForm.get('rating').value,
}; };
if (this.ratingForm.get('review').value !== '') { if (this.ratingForm.get('review').value !== '') {
@@ -59,8 +65,8 @@ export class AdvancedWorkflowActionRatingComponent extends AdvancedWorkflowActio
return ADVANCED_WORKFLOW_ACTION_RATING; return ADVANCED_WORKFLOW_ACTION_RATING;
} }
getAdvancedInfo(workflowAction: WorkflowAction | null): RatingReviewerActionAdvancedInfo | null { getAdvancedInfo(workflowAction: WorkflowAction | null): RatingAdvancedWorkflowInfo | null {
return workflowAction ? (workflowAction.advancedInfo[0] as RatingReviewerActionAdvancedInfo) : null; return workflowAction ? (workflowAction.advancedInfo[0] as RatingAdvancedWorkflowInfo) : null;
} }
} }

View File

@@ -1,6 +1,9 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AdvancedWorkflowActionSelectReviewerComponent } from './advanced-workflow-action-select-reviewer.component'; import {
import { ActivatedRoute, convertToParamMap } from '@angular/router'; AdvancedWorkflowActionSelectReviewerComponent,
ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER
} from './advanced-workflow-action-select-reviewer.component';
import { ActivatedRoute } from '@angular/router';
import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service'; import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service';
import { WorkflowItemDataServiceStub } from '../../../shared/testing/workflow-item-data-service.stub'; import { WorkflowItemDataServiceStub } from '../../../shared/testing/workflow-item-data-service.stub';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
@@ -14,10 +17,19 @@ import { TranslateModule } from '@ngx-translate/core';
import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service'; import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service';
import { ClaimedTaskDataServiceStub } from '../../../shared/testing/claimed-task-data-service.stub'; import { ClaimedTaskDataServiceStub } from '../../../shared/testing/claimed-task-data-service.stub';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { WorkflowItem } from '../../../core/submission/models/workflowitem.model';
import { createSuccessfulRemoteDataObject$, createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils';
import { Item } from '../../../core/shared/item.model';
import { EPersonMock, EPersonMock2 } from '../../../shared/testing/eperson.mock';
import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response';
import { NO_ERRORS_SCHEMA } from '@angular/core';
const claimedTaskId = '2';
const workflowId = '1'; const workflowId = '1';
describe('AdvancedWorkflowActionSelectReviewerComponent', () => { describe('AdvancedWorkflowActionSelectReviewerComponent', () => {
const workflowItem: WorkflowItem = new WorkflowItem();
workflowItem.item = createSuccessfulRemoteDataObject$(new Item());
let component: AdvancedWorkflowActionSelectReviewerComponent; let component: AdvancedWorkflowActionSelectReviewerComponent;
let fixture: ComponentFixture<AdvancedWorkflowActionSelectReviewerComponent>; let fixture: ComponentFixture<AdvancedWorkflowActionSelectReviewerComponent>;
@@ -46,11 +58,13 @@ describe('AdvancedWorkflowActionSelectReviewerComponent', () => {
useValue: { useValue: {
data: observableOf({ data: observableOf({
id: workflowId, id: workflowId,
wfi: createSuccessfulRemoteDataObject(workflowItem),
}), }),
snapshot: { snapshot: {
queryParams: convertToParamMap({ queryParams: {
claimedTask: claimedTaskId,
workflow: 'testaction', workflow: 'testaction',
}), },
}, },
}, },
}, },
@@ -60,6 +74,7 @@ describe('AdvancedWorkflowActionSelectReviewerComponent', () => {
{ provide: WorkflowActionDataService, useValue: workflowActionDataService }, { provide: WorkflowActionDataService, useValue: workflowActionDataService },
{ provide: WorkflowItemDataService, useValue: workflowItemDataService }, { provide: WorkflowItemDataService, useValue: workflowItemDataService },
], ],
schemas: [NO_ERRORS_SCHEMA],
}).compileComponents(); }).compileComponents();
}); });
@@ -67,9 +82,49 @@ describe('AdvancedWorkflowActionSelectReviewerComponent', () => {
fixture = TestBed.createComponent(AdvancedWorkflowActionSelectReviewerComponent); fixture = TestBed.createComponent(AdvancedWorkflowActionSelectReviewerComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
spyOn(component, 'previousPage');
}); });
it('should create', () => { afterEach(() => {
expect(component).toBeTruthy(); fixture.debugElement.nativeElement.remove();
});
describe('performAction', () => {
it('should call the claimedTaskDataService with the list of selected ePersons', () => {
spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true)));
component.selectedReviewers = [EPersonMock, EPersonMock2];
component.performAction();
expect(claimedTaskDataService.submitTask).toHaveBeenCalledWith(claimedTaskId, {
[ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER]: true,
eperson: [EPersonMock.id, EPersonMock2.id],
});
expect(notificationService.success).toHaveBeenCalled();
expect(component.previousPage).toHaveBeenCalled();
});
it('should not call the claimedTaskDataService with the list of selected ePersons when it\'s empty', () => {
spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true)));
component.selectedReviewers = [];
component.performAction();
expect(claimedTaskDataService.submitTask).not.toHaveBeenCalled();
});
it('should not call the return to mydspace page when the request failed', () => {
spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(false)));
component.selectedReviewers = [EPersonMock, EPersonMock2];
component.performAction();
expect(claimedTaskDataService.submitTask).toHaveBeenCalledWith(claimedTaskId, {
[ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER]: true,
eperson: [EPersonMock.id, EPersonMock2.id],
});
expect(notificationService.error).toHaveBeenCalled();
expect(component.previousPage).not.toHaveBeenCalled();
});
}); });
}); });

View File

@@ -5,17 +5,20 @@ import {
import { AdvancedWorkflowActionComponent } from '../advanced-workflow-action/advanced-workflow-action.component'; import { AdvancedWorkflowActionComponent } from '../advanced-workflow-action/advanced-workflow-action.component';
import { WorkflowAction } from '../../../core/tasks/models/workflow-action-object.model'; import { WorkflowAction } from '../../../core/tasks/models/workflow-action-object.model';
import { import {
SelectReviewerActionAdvancedInfo SelectReviewerAdvancedWorkflowInfo
} from '../../../core/tasks/models/select-reviewer-action-advanced-info.model'; } from '../../../core/tasks/models/select-reviewer-advanced-workflow-info.model';
import { import {
EPersonListActionConfig EPersonListActionConfig
} from '../../../access-control/group-registry/group-form/members-list/members-list.component'; } from '../../../access-control/group-registry/group-form/members-list/members-list.component';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { EPerson } from '../../../core/eperson/models/eperson.model'; import { EPerson } from '../../../core/eperson/models/eperson.model';
export const WORKFLOW_ADVANCED_TASK_OPTION_SELECT_REVIEWER = 'submit_select_reviewer'; export const ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER = 'submit_select_reviewer';
export const ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER = 'selectrevieweraction'; export const ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER = 'selectrevieweraction';
/**
* The page on which Review Managers can assign Reviewers to review an item.
*/
@rendersAdvancedWorkflowTaskOption(ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER) @rendersAdvancedWorkflowTaskOption(ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER)
@Component({ @Component({
selector: 'ds-advanced-workflow-action-select-reviewer', selector: 'ds-advanced-workflow-action-select-reviewer',
@@ -75,7 +78,7 @@ export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkf
} }
this.subs.push(this.workflowAction$.subscribe((workflowAction: WorkflowAction) => { this.subs.push(this.workflowAction$.subscribe((workflowAction: WorkflowAction) => {
if (workflowAction) { if (workflowAction) {
this.groupId = (workflowAction.advancedInfo as SelectReviewerActionAdvancedInfo[])[0].group; this.groupId = (workflowAction.advancedInfo as SelectReviewerAdvancedWorkflowInfo[])[0].group;
} else { } else {
this.groupId = null; this.groupId = null;
} }
@@ -86,18 +89,23 @@ export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkf
return ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER; return ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER;
} }
/**
* Only performs the action when some reviewers have been selected.
*/
performAction(): void { performAction(): void {
if (this.selectedReviewers.length > 0) { if (this.selectedReviewers.length > 0) {
super.performAction(); super.performAction();
} else { } else {
this.displayError = true; this.displayError = true;
} }
console.log(this.displayError);
} }
/**
* Returns the task option and the selected {@link EPerson} id(s)
*/
createBody(): any { createBody(): any {
return { return {
[WORKFLOW_ADVANCED_TASK_OPTION_SELECT_REVIEWER]: true, [ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER]: true,
eperson: this.selectedReviewers.map((ePerson: EPerson) => ePerson.id), eperson: this.selectedReviewers.map((ePerson: EPerson) => ePerson.id),
}; };
} }

View File

@@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NO_ERRORS_SCHEMA, SimpleChange } from '@angular/core'; import { NO_ERRORS_SCHEMA, SimpleChange, DebugElement } from '@angular/core';
import { ComponentFixture, fakeAsync, flush, inject, TestBed, waitForAsync } from '@angular/core/testing'; import { ComponentFixture, fakeAsync, flush, TestBed, waitForAsync } from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule, By } from '@angular/platform-browser'; import { BrowserModule, By } from '@angular/platform-browser';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
@@ -31,6 +31,7 @@ import { NotificationsServiceStub } from '../../../../shared/testing/notificatio
import { RouterMock } from '../../../../shared/mocks/router.mock'; import { RouterMock } from '../../../../shared/mocks/router.mock';
import { PaginationService } from '../../../../core/pagination/pagination.service'; import { PaginationService } from '../../../../core/pagination/pagination.service';
import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub'; import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub';
import { EpersonDtoModel } from '../../../../core/eperson/models/eperson-dto.model';
describe('ReviewersListComponent', () => { describe('ReviewersListComponent', () => {
let component: ReviewersListComponent; let component: ReviewersListComponent;
@@ -45,6 +46,8 @@ describe('ReviewersListComponent', () => {
let epersonMembers; let epersonMembers;
let subgroupMembers; let subgroupMembers;
let paginationService; let paginationService;
let ePersonDtoModel1: EpersonDtoModel;
let ePersonDtoModel2: EpersonDtoModel;
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
activeGroup = GroupMock; activeGroup = GroupMock;
@@ -119,7 +122,6 @@ describe('ReviewersListComponent', () => {
findById(id: string) { findById(id: string) {
for (const group of allGroups) { for (const group of allGroups) {
if (group.id === id) { if (group.id === id) {
console.log('found', group);
return createSuccessfulRemoteDataObject$(group); return createSuccessfulRemoteDataObject$(group);
} }
} }
@@ -167,9 +169,12 @@ describe('ReviewersListComponent', () => {
fixture.debugElement.nativeElement.remove(); fixture.debugElement.nativeElement.remove();
})); }));
it('should create ReviewersListComponent', inject([ReviewersListComponent], (comp: ReviewersListComponent) => { beforeEach(() => {
expect(comp).toBeDefined(); ePersonDtoModel1 = new EpersonDtoModel();
})); ePersonDtoModel1.eperson = EPersonMock;
ePersonDtoModel2 = new EpersonDtoModel();
ePersonDtoModel2.eperson = EPersonMock2;
});
describe('when no group is selected', () => { describe('when no group is selected', () => {
beforeEach(() => { beforeEach(() => {
@@ -179,18 +184,18 @@ describe('ReviewersListComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
}); });
it('should show no epersons because no group is selected', () => { it('should show no ePersons because no group is selected', () => {
const epersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child')); const ePersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child'));
expect(epersonIdsFound.length).toEqual(0); expect(ePersonIdsFound.length).toEqual(0);
epersonMembers.map((eperson: EPerson) => { epersonMembers.map((ePerson: EPerson) => {
expect(epersonIdsFound.find((foundEl) => { expect(ePersonIdsFound.find((foundEl) => {
return (foundEl.nativeElement.textContent.trim() === eperson.uuid); return (foundEl.nativeElement.textContent.trim() === ePerson.uuid);
})).not.toBeTruthy(); })).not.toBeTruthy();
}); });
}); });
}); });
describe('when group is selected', () => { describe('when a group is selected', () => {
beforeEach(() => { beforeEach(() => {
component.ngOnChanges({ component.ngOnChanges({
groupId: new SimpleChange(undefined, GroupMock.id, true) groupId: new SimpleChange(undefined, GroupMock.id, true)
@@ -198,15 +203,50 @@ describe('ReviewersListComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
}); });
it('should show all eperson members of group', () => { it('should show all ePerson members of group', () => {
const epersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child')); const ePersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child'));
expect(epersonIdsFound.length).toEqual(1); expect(ePersonIdsFound.length).toEqual(1);
epersonMembers.map((eperson: EPerson) => { epersonMembers.map((ePerson: EPerson) => {
expect(epersonIdsFound.find((foundEl) => { expect(ePersonIdsFound.find((foundEl: DebugElement) => {
return (foundEl.nativeElement.textContent.trim() === eperson.uuid); return (foundEl.nativeElement.textContent.trim() === ePerson.uuid);
})).toBeTruthy(); })).toBeTruthy();
}); });
}); });
}); });
it('should replace the value when a new member is added when multipleReviewers is false', () => {
spyOn(component.selectedReviewersUpdated, 'emit');
component.multipleReviewers = false;
component.selectedReviewers = [ePersonDtoModel1];
component.addMemberToGroup(ePersonDtoModel2);
expect(component.selectedReviewers).toEqual([ePersonDtoModel2]);
expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([ePersonDtoModel2.eperson]);
});
it('should add the value when a new member is added when multipleReviewers is true', () => {
spyOn(component.selectedReviewersUpdated, 'emit');
component.multipleReviewers = true;
component.selectedReviewers = [ePersonDtoModel1];
component.addMemberToGroup(ePersonDtoModel2);
expect(component.selectedReviewers).toEqual([ePersonDtoModel1, ePersonDtoModel2]);
expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([ePersonDtoModel1.eperson, ePersonDtoModel2.eperson]);
});
it('should delete the member when present', () => {
spyOn(component.selectedReviewersUpdated, 'emit');
ePersonDtoModel1.memberOfGroup = true;
component.selectedReviewers = [ePersonDtoModel1];
component.deleteMemberFromGroup(ePersonDtoModel1);
expect(component.selectedReviewers).toEqual([]);
expect(ePersonDtoModel1.memberOfGroup).toBeFalse();
expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([]);
});
}); });

View File

@@ -14,7 +14,8 @@ import { Observable, of as observableOf } from 'rxjs';
import { hasValue } from '../../../../shared/empty.util'; import { hasValue } from '../../../../shared/empty.util';
import { PaginatedList } from '../../../../core/data/paginated-list.model'; import { PaginatedList } from '../../../../core/data/paginated-list.model';
import { import {
MembersListComponent, EPersonListActionConfig MembersListComponent,
EPersonListActionConfig,
} from '../../../../access-control/group-registry/group-form/members-list/members-list.component'; } from '../../../../access-control/group-registry/group-form/members-list/members-list.component';
/** /**
@@ -26,6 +27,9 @@ enum SubKey {
SearchResultsDTO, SearchResultsDTO,
} }
/**
* A custom {@link MembersListComponent} for the advanced SelectReviewer workflow.
*/
@Component({ @Component({
selector: 'ds-reviewers-list', selector: 'ds-reviewers-list',
// templateUrl: './reviewers-list.component.html', // templateUrl: './reviewers-list.component.html',
@@ -83,6 +87,12 @@ export class ReviewersListComponent extends MembersListComponent implements OnIn
} }
} }
/**
* Sets the list of currently selected members, when no group is defined the list of {@link selectedReviewers}
* will be set.
*
* @param page The number of the page to retrieve
*/
retrieveMembers(page: number): void { retrieveMembers(page: number): void {
this.config.currentPage = page; this.config.currentPage = page;
if (this.groupId === null) { if (this.groupId === null) {
@@ -95,19 +105,35 @@ export class ReviewersListComponent extends MembersListComponent implements OnIn
} }
} }
/**
* Checks whether the given {@link possibleMember} is part of the {@link selectedReviewers}.
*
* @param possibleMember The {@link EPerson} that needs to be checked
*/
isMemberOfGroup(possibleMember: EPerson): Observable<boolean> { isMemberOfGroup(possibleMember: EPerson): Observable<boolean> {
return observableOf(hasValue(this.selectedReviewers.find((reviewer: EpersonDtoModel) => reviewer.eperson.id === possibleMember.id))); return observableOf(hasValue(this.selectedReviewers.find((reviewer: EpersonDtoModel) => reviewer.eperson.id === possibleMember.id)));
} }
/**
* Removes the {@link ePerson} from the {@link selectedReviewers}
*
* @param ePerson The {@link EpersonDtoModel} containg the {@link EPerson} to remove
*/
deleteMemberFromGroup(ePerson: EpersonDtoModel) { deleteMemberFromGroup(ePerson: EpersonDtoModel) {
ePerson.memberOfGroup = false; ePerson.memberOfGroup = false;
const index = this.selectedReviewers.indexOf(ePerson); const index = this.selectedReviewers.indexOf(ePerson);
if (index !== -1) { if (index !== -1) {
this.selectedReviewers.splice(index, 1); this.selectedReviewers.splice(index, 1);
} }
this.selectedReviewersUpdated.emit(this.selectedReviewers.map((epersonDtoModel: EpersonDtoModel) => epersonDtoModel.eperson)); this.selectedReviewersUpdated.emit(this.selectedReviewers.map((ePersonDtoModel: EpersonDtoModel) => ePersonDtoModel.eperson));
} }
/**
* Adds the {@link ePerson} to the {@link selectedReviewers} (or replaces it when {@link multipleReviewers} is
* `false`). Afterwards it will emit the list.
*
* @param ePerson The {@link EPerson} to add to the list
*/
addMemberToGroup(ePerson: EpersonDtoModel) { addMemberToGroup(ePerson: EpersonDtoModel) {
ePerson.memberOfGroup = true; ePerson.memberOfGroup = true;
if (!this.multipleReviewers) { if (!this.multipleReviewers) {

View File

@@ -5,7 +5,7 @@ import { MockComponent } from 'ng-mocks';
import { DSOSelectorComponent } from '../../../shared/dso-selector/dso-selector/dso-selector.component'; import { DSOSelectorComponent } from '../../../shared/dso-selector/dso-selector/dso-selector.component';
import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service'; import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service';
import { ClaimedTaskDataServiceStub } from '../../../shared/testing/claimed-task-data-service.stub'; import { ClaimedTaskDataServiceStub } from '../../../shared/testing/claimed-task-data-service.stub';
import { ActivatedRoute, convertToParamMap } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service'; import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
@@ -54,9 +54,9 @@ describe('AdvancedWorkflowActionComponent', () => {
id: workflowId, id: workflowId,
}), }),
snapshot: { snapshot: {
queryParams: convertToParamMap({ queryParams: {
workflow: 'testaction', workflow: 'testaction',
}), },
}, },
}, },
}, },
@@ -108,7 +108,8 @@ describe('AdvancedWorkflowActionComponent', () => {
}); });
@Component({ @Component({
selector: 'ds-test-cmp', // eslint-disable-next-line @angular-eslint/component-selector
selector: '',
template: '' template: ''
}) })
class TestComponent extends AdvancedWorkflowActionComponent { class TestComponent extends AdvancedWorkflowActionComponent {

View File

@@ -13,6 +13,12 @@ import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.se
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response'; import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response';
/**
* Abstract component for rendering an advanced claimed task's workflow page
* To create a child-component for a new option:
* - Set the "getType()" of the component
* - Implement the createBody, should always contain at least the ADVANCED_WORKFLOW_TASK_OPTION
*/
@Component({ @Component({
selector: 'ds-advanced-workflow-action', selector: 'ds-advanced-workflow-action',
template: '', template: '',
@@ -62,7 +68,7 @@ export abstract class AdvancedWorkflowActionComponent extends WorkflowItemAction
/** /**
* Submits the task with the given {@link createBody}. * Submits the task with the given {@link createBody}.
* *
* @param id * @param id The task id
*/ */
sendRequest(id: string): Observable<boolean> { sendRequest(id: string): Observable<boolean> {
return this.claimedTaskDataService.submitTask(id, this.createBody()).pipe( return this.claimedTaskDataService.submitTask(id, this.createBody()).pipe(

View File

@@ -1 +1 @@
<ng-template dsAdvancedClaimedTaskActions></ng-template> <ng-template dsAdvancedWorkflowActions></ng-template>

View File

@@ -2,6 +2,15 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AdvancedWorkflowActionsLoaderComponent } from './advanced-workflow-actions-loader.component'; import { AdvancedWorkflowActionsLoaderComponent } from './advanced-workflow-actions-loader.component';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { RouterStub } from '../../../shared/testing/router.stub'; import { RouterStub } from '../../../shared/testing/router.stub';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { AdvancedWorkflowActionsDirective } from './advanced-workflow-actions.directive';
import {
rendersAdvancedWorkflowTaskOption
} from '../../../shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator';
import { By } from '@angular/platform-browser';
import { PAGE_NOT_FOUND_PATH } from '../../../app-routing-paths';
const ADVANCED_WORKFLOW_ACTION_TEST = 'testaction';
describe('AdvancedWorkflowActionsLoaderComponent', () => { describe('AdvancedWorkflowActionsLoaderComponent', () => {
let component: AdvancedWorkflowActionsLoaderComponent; let component: AdvancedWorkflowActionsLoaderComponent;
@@ -14,21 +23,61 @@ describe('AdvancedWorkflowActionsLoaderComponent', () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ declarations: [
AdvancedWorkflowActionsDirective,
AdvancedWorkflowActionsLoaderComponent, AdvancedWorkflowActionsLoaderComponent,
], ],
providers: [ providers: [
{ provide: Router, useValue: router }, { provide: Router, useValue: router },
], ],
}).overrideComponent(AdvancedWorkflowActionsLoaderComponent, {
set: {
changeDetection: ChangeDetectionStrategy.Default,
entryComponents: [AdvancedWorkflowActionTestComponent],
},
}).compileComponents(); }).compileComponents();
}); });
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(AdvancedWorkflowActionsLoaderComponent); fixture = TestBed.createComponent(AdvancedWorkflowActionsLoaderComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
component.type = ADVANCED_WORKFLOW_ACTION_TEST;
fixture.detectChanges(); fixture.detectChanges();
}); });
it('should create', () => { afterEach(() => {
expect(component).toBeTruthy(); fixture.debugElement.nativeElement.remove();
});
describe('When the component is rendered', () => {
it('should display the AdvancedWorkflowActionTestComponent when the type has been defined in a rendersAdvancedWorkflowTaskOption', () => {
spyOn(component, 'getComponentByWorkflowTaskOption').and.returnValue(AdvancedWorkflowActionTestComponent);
component.ngOnInit();
fixture.detectChanges();
expect(component.getComponentByWorkflowTaskOption).toHaveBeenCalledWith(ADVANCED_WORKFLOW_ACTION_TEST);
expect(fixture.debugElement.query(By.css('#AdvancedWorkflowActionsLoaderComponent'))).not.toBeNull();
expect(router.navigate).not.toHaveBeenCalled();
});
it('should redirect to page not found when the type has not been defined in a rendersAdvancedWorkflowTaskOption', () => {
spyOn(component, 'getComponentByWorkflowTaskOption').and.returnValue(undefined);
component.type = 'nonexistingaction';
component.ngOnInit();
fixture.detectChanges();
expect(component.getComponentByWorkflowTaskOption).toHaveBeenCalledWith('nonexistingaction');
expect(router.navigate).toHaveBeenCalledWith([PAGE_NOT_FOUND_PATH]);
});
}); });
}); });
@rendersAdvancedWorkflowTaskOption(ADVANCED_WORKFLOW_ACTION_TEST)
@Component({
// eslint-disable-next-line @angular-eslint/component-selector
selector: '',
template: '<span id="AdvancedWorkflowActionsLoaderComponent"></span>',
})
class AdvancedWorkflowActionTestComponent {
}

View File

@@ -3,10 +3,13 @@ import { hasValue } from '../../../shared/empty.util';
import { import {
getAdvancedComponentByWorkflowTaskOption getAdvancedComponentByWorkflowTaskOption
} from '../../../shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator'; } from '../../../shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator';
import { AdvancedClaimedTaskActionsDirective } from './advanced-claimed-task-actions.directive'; import { AdvancedWorkflowActionsDirective } from './advanced-workflow-actions.directive';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { PAGE_NOT_FOUND_PATH } from '../../../app-routing-paths'; import { PAGE_NOT_FOUND_PATH } from '../../../app-routing-paths';
/**
* Component for loading a {@link AdvancedWorkflowActionComponent} depending on the "{@link type}" input
*/
@Component({ @Component({
selector: 'ds-advanced-workflow-actions-loader', selector: 'ds-advanced-workflow-actions-loader',
templateUrl: './advanced-workflow-actions-loader.component.html', templateUrl: './advanced-workflow-actions-loader.component.html',
@@ -23,7 +26,7 @@ export class AdvancedWorkflowActionsLoaderComponent implements OnInit {
/** /**
* Directive to determine where the dynamic child component is located * Directive to determine where the dynamic child component is located
*/ */
@ViewChild(AdvancedClaimedTaskActionsDirective, { static: true }) claimedTaskActionsDirective: AdvancedClaimedTaskActionsDirective; @ViewChild(AdvancedWorkflowActionsDirective, { static: true }) claimedTaskActionsDirective: AdvancedWorkflowActionsDirective;
constructor( constructor(
private componentFactoryResolver: ComponentFactoryResolver, private componentFactoryResolver: ComponentFactoryResolver,
@@ -47,8 +50,8 @@ export class AdvancedWorkflowActionsLoaderComponent implements OnInit {
} }
} }
getComponentByWorkflowTaskOption(option: string) { getComponentByWorkflowTaskOption(type: string): any {
return getAdvancedComponentByWorkflowTaskOption(option); return getAdvancedComponentByWorkflowTaskOption(type);
} }
} }

View File

@@ -1,12 +1,12 @@
import { Directive, ViewContainerRef } from '@angular/core'; import { Directive, ViewContainerRef } from '@angular/core';
@Directive({ @Directive({
selector: '[dsAdvancedClaimedTaskActions]', selector: '[dsAdvancedWorkflowActions]',
}) })
/** /**
* Directive used as a hook to know where to inject the dynamic Advanced Claimed Task Actions component * Directive used as a hook to know where to inject the dynamic Advanced Claimed Task Actions component
*/ */
export class AdvancedClaimedTaskActionsDirective { export class AdvancedWorkflowActionsDirective {
constructor( constructor(
public viewContainerRef: ViewContainerRef, public viewContainerRef: ViewContainerRef,

View File

@@ -24,8 +24,8 @@ import {
AdvancedWorkflowActionPageComponent AdvancedWorkflowActionPageComponent
} from './advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component'; } from './advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component';
import { import {
AdvancedClaimedTaskActionsDirective AdvancedWorkflowActionsDirective
} from './advanced-workflow-action/advanced-workflow-actions-loader/advanced-claimed-task-actions.directive'; } from './advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions.directive';
import { AccessControlModule } from '../access-control/access-control.module'; import { AccessControlModule } from '../access-control/access-control.module';
import { import {
ReviewersListComponent ReviewersListComponent
@@ -54,7 +54,7 @@ import { RatingModule } from 'ngx-bootstrap/rating';
AdvancedWorkflowActionRatingComponent, AdvancedWorkflowActionRatingComponent,
AdvancedWorkflowActionSelectReviewerComponent, AdvancedWorkflowActionSelectReviewerComponent,
AdvancedWorkflowActionPageComponent, AdvancedWorkflowActionPageComponent,
AdvancedClaimedTaskActionsDirective, AdvancedWorkflowActionsDirective,
ReviewersListComponent, ReviewersListComponent,
] ]
}) })