Merge remote-tracking branch 'atmire/w2p-98211_advanced-workflow-actions-7.2' into w2p-98211_advanced-workflow-actions-main

# Conflicts:
#	src/app/my-dspace-page/my-dspace-search.module.ts
#	src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html
#	src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-approved-search-result/claimed-approved-search-result-list-element.component.html
#	src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-approved-search-result/claimed-approved-search-result-list-element.component.spec.ts
#	src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-search-result/claimed-declined-search-result-list-element.component.html
#	src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-search-result/claimed-declined-search-result-list-element.component.spec.ts
This commit is contained in:
Alexandre Vryghem
2023-02-06 23:16:08 +01:00
31 changed files with 338 additions and 56 deletions

View File

@@ -37,12 +37,30 @@ enum SubKey {
SearchResultsDTO, SearchResultsDTO,
} }
/**
* The layout config of the buttons in the last column
*/
export interface EPersonActionConfig { export interface EPersonActionConfig {
/**
* The css classes that should be added to the button
*/
css?: string; css?: string;
/**
* Whether the button should be disabled
*/
disabled: boolean; disabled: boolean;
/**
* The Font Awesome icon that should be used
*/
icon: string; icon: string;
} }
/**
* The {@link EPersonActionConfig} that should be used to display the button. The remove config will be used when the
* {@link EPerson} is already a member of the {@link Group} and the remove config will be used otherwise.
*
* *See {@link actionConfig} for an example*
*/
export interface EPersonListActionConfig { export interface EPersonListActionConfig {
add: EPersonActionConfig; add: EPersonActionConfig;
remove: EPersonActionConfig; remove: EPersonActionConfig;

View File

@@ -25,6 +25,7 @@ import { ThemedItemListPreviewComponent } from '../shared/object-list/my-dspace-
import { MyDSpaceItemStatusComponent } from '../shared/object-collection/shared/mydspace-item-status/my-dspace-item-status.component'; import { MyDSpaceItemStatusComponent } from '../shared/object-collection/shared/mydspace-item-status/my-dspace-item-status.component';
import { JournalEntitiesModule } from '../entity-groups/journal-entities/journal-entities.module'; import { JournalEntitiesModule } from '../entity-groups/journal-entities/journal-entities.module';
import { MyDSpaceActionsModule } from '../shared/mydspace-actions/mydspace-actions.module'; import { MyDSpaceActionsModule } from '../shared/mydspace-actions/mydspace-actions.module';
import { ClaimedDeclinedTaskSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-task-search-result/claimed-declined-task-search-result-list-element.component';
const ENTRY_COMPONENTS = [ const ENTRY_COMPONENTS = [
WorkspaceItemSearchResultListElementComponent, WorkspaceItemSearchResultListElementComponent,
@@ -32,6 +33,7 @@ const ENTRY_COMPONENTS = [
ClaimedSearchResultListElementComponent, ClaimedSearchResultListElementComponent,
ClaimedApprovedSearchResultListElementComponent, ClaimedApprovedSearchResultListElementComponent,
ClaimedDeclinedSearchResultListElementComponent, ClaimedDeclinedSearchResultListElementComponent,
ClaimedDeclinedTaskSearchResultListElementComponent,
PoolSearchResultListElementComponent, PoolSearchResultListElementComponent,
ItemSearchResultDetailElementComponent, ItemSearchResultDetailElementComponent,
WorkspaceItemSearchResultDetailElementComponent, WorkspaceItemSearchResultDetailElementComponent,

View File

@@ -7,8 +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" and "workflowType" of the component * - Set the "{@link option}" and "{@link workflowType}" of the component
* - Add a @rendersWorkflowTaskOption annotation to your component providing the same enum value * - Add a @{@link rendersWorkflowTaskOption} annotation to your component providing the same enum value
*/ */
@Component({ @Component({
selector: 'ds-advanced-claimed-task-action-abstract', selector: 'ds-advanced-claimed-task-action-abstract',

View File

@@ -1,8 +0,0 @@
import { Component } from '@angular/core';
@Component({
selector: 'ds-advanced-workflow-action-abstract',
template: '',
})
export abstract class AdvancedWorkflowActionAbstractComponent {
}

View File

@@ -12,12 +12,5 @@
[routerLink]="[getWorkflowItemViewRoute(workflowitem)]"> [routerLink]="[getWorkflowItemViewRoute(workflowitem)]">
<i class="fa fa-info-circle"></i> {{"submission.workflow.generic.view" | translate}} <i class="fa fa-info-circle"></i> {{"submission.workflow.generic.view" | translate}}
</button> </button>
<ds-claimed-task-actions-loader [item]="item"
[option]="returnToPoolOption"
[object]="object"
[workflowitem]="workflowitem"
(processCompleted)="this.processCompleted.emit($event)">
</ds-claimed-task-actions-loader>
</div> </div>
</ng-container> </ng-container>

View File

@@ -14,7 +14,6 @@ import { RequestService } from '../../../core/data/request.service';
import { SearchService } from '../../../core/shared/search/search.service'; import { SearchService } from '../../../core/shared/search/search.service';
import { WorkflowAction } from '../../../core/tasks/models/workflow-action-object.model'; import { WorkflowAction } from '../../../core/tasks/models/workflow-action-object.model';
import { WorkflowActionDataService } from '../../../core/data/workflow-action-data.service'; import { WorkflowActionDataService } from '../../../core/data/workflow-action-data.service';
import { WORKFLOW_TASK_OPTION_RETURN_TO_POOL } from './return-to-pool/claimed-task-actions-return-to-pool.component';
import { getWorkflowItemViewRoute } from '../../../workflowitems-edit-page/workflowitems-edit-page-routing-paths'; import { getWorkflowItemViewRoute } from '../../../workflowitems-edit-page/workflowitems-edit-page-routing-paths';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
@@ -48,12 +47,6 @@ export class ClaimedTaskActionsComponent extends MyDSpaceActionsComponent<Claime
*/ */
public actionRD$: Observable<RemoteData<WorkflowAction>>; public actionRD$: Observable<RemoteData<WorkflowAction>>;
/**
* The option used to render the "return to pool" component
* Every claimed task contains this option
*/
public returnToPoolOption = WORKFLOW_TASK_OPTION_RETURN_TO_POOL;
/** /**
* Initialize instance variables * Initialize instance variables
* *

View File

@@ -6,6 +6,12 @@ import { NotificationsService } from '../../../notifications/notifications.servi
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { SearchService } from '../../../../core/shared/search/search.service'; import { SearchService } from '../../../../core/shared/search/search.service';
import { RequestService } from '../../../../core/data/request.service'; import { RequestService } from '../../../../core/data/request.service';
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
import {
ClaimedDeclinedTaskTaskSearchResult
} from '../../../object-collection/shared/claimed-declined-task-task-search-result.model';
import { Observable, of as observableOf } from 'rxjs';
import { RemoteData } from 'src/app/core/data/remote-data';
export const WORKFLOW_TASK_OPTION_DECLINE_TASK = 'submit_decline_task'; export const WORKFLOW_TASK_OPTION_DECLINE_TASK = 'submit_decline_task';
@@ -31,4 +37,14 @@ export class ClaimedTaskActionsDeclineTaskComponent extends ClaimedTaskActionsAb
super(injector, router, notificationsService, translate, searchService, requestService); super(injector, router, notificationsService, translate, searchService, requestService);
} }
reloadObjectExecution(): Observable<RemoteData<DSpaceObject> | DSpaceObject> {
return observableOf(this.object);
}
convertReloadedObject(dso: DSpaceObject): DSpaceObject {
return Object.assign(new ClaimedDeclinedTaskTaskSearchResult(), dso, {
indexableObject: dso
});
}
} }

View File

@@ -2,7 +2,7 @@ import { ClaimedTask } from '../../../core/tasks/models/claimed-task-object.mode
import { SearchResult } from '../../search/models/search-result.model'; import { SearchResult } from '../../search/models/search-result.model';
/** /**
* Represents a search result object of a Declined ClaimedTask object * Represents a search result object of a Declined/Rejected ClaimedTask object (sent back to the submitter)
*/ */
export class ClaimedDeclinedTaskSearchResult extends SearchResult<ClaimedTask> { export class ClaimedDeclinedTaskSearchResult extends SearchResult<ClaimedTask> {
} }

View File

@@ -0,0 +1,8 @@
import { ClaimedTask } from '../../../core/tasks/models/claimed-task-object.model';
import { SearchResult } from '../../search/models/search-result.model';
/**
* Represents a search result object of a Declined ClaimedTask object (sent back to the Review Managers)
*/
export class ClaimedDeclinedTaskTaskSearchResult extends SearchResult<ClaimedTask> {
}

View File

@@ -6,4 +6,5 @@ export enum MyDspaceItemStatusType {
ARCHIVED = 'mydspace.status.archived', ARCHIVED = 'mydspace.status.archived',
DECLINED = 'mydspace.status.declined', DECLINED = 'mydspace.status.declined',
APPROVED = 'mydspace.status.approved', APPROVED = 'mydspace.status.approved',
DECLINED_TASk = 'mydspace.status.declined-task',
} }

View File

@@ -1,6 +1,6 @@
<ng-container *ngVar="(workflowitemRD$ | async)?.payload as workflowitem"> <ng-container *ngVar="(workflowitemRD$ | async)?.payload as workflowitem">
<div class="alert alert-success w-100" role="alert"> <div class="alert alert-success w-100" role="alert">
<h4 class="alert-heading">Approved</h4> <h4 class="alert-heading mb-0">{{ 'claimed-approved-search-result-list-element.title' | translate }}</h4>
<ds-themed-item-list-preview *ngIf="workflowitem" <ds-themed-item-list-preview *ngIf="workflowitem"
[item]="(workflowitem?.item | async)?.payload" [item]="(workflowitem?.item | async)?.payload"
[object]="object" [object]="object"

View File

@@ -19,6 +19,7 @@ import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service
import { DSONameServiceMock } from '../../../../mocks/dso-name.service.mock'; import { DSONameServiceMock } from '../../../../mocks/dso-name.service.mock';
import { APP_CONFIG } from '../../../../../../config/app-config.interface'; import { APP_CONFIG } from '../../../../../../config/app-config.interface';
import { environment } from '../../../../../../environments/environment'; import { environment } from '../../../../../../environments/environment';
import { TranslateModule } from '@ngx-translate/core';
let component: ClaimedApprovedSearchResultListElementComponent; let component: ClaimedApprovedSearchResultListElementComponent;
let fixture: ComponentFixture<ClaimedApprovedSearchResultListElementComponent>; let fixture: ComponentFixture<ClaimedApprovedSearchResultListElementComponent>;
@@ -64,7 +65,10 @@ const linkService = getMockLinkService();
describe('ClaimedApprovedSearchResultListElementComponent', () => { describe('ClaimedApprovedSearchResultListElementComponent', () => {
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [NoopAnimationsModule], imports: [
TranslateModule.forRoot(),
NoopAnimationsModule,
],
declarations: [ClaimedApprovedSearchResultListElementComponent, VarDirective], declarations: [ClaimedApprovedSearchResultListElementComponent, VarDirective],
providers: [ providers: [
{ provide: TruncatableService, useValue: {} }, { provide: TruncatableService, useValue: {} },

View File

@@ -1,6 +1,6 @@
<ng-container *ngVar="(workflowitemRD$ | async)?.payload as workflowitem"> <ng-container *ngVar="(workflowitemRD$ | async)?.payload as workflowitem">
<div class="alert alert-secondary w-100" role="alert"> <div class="alert alert-secondary w-100" role="alert">
<h4 class="alert-heading">Declined</h4> <h4 class="alert-heading mb-0">{{ 'claimed-declined-search-result-list-element.title' | translate }}</h4>
<ds-themed-item-list-preview *ngIf="workflowitem" <ds-themed-item-list-preview *ngIf="workflowitem"
[item]="(workflowitem?.item | async)?.payload" [item]="(workflowitem?.item | async)?.payload"
[object]="object" [object]="object"

View File

@@ -19,6 +19,7 @@ import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service
import { DSONameServiceMock } from '../../../../mocks/dso-name.service.mock'; import { DSONameServiceMock } from '../../../../mocks/dso-name.service.mock';
import { APP_CONFIG } from '../../../../../../config/app-config.interface'; import { APP_CONFIG } from '../../../../../../config/app-config.interface';
import { environment } from '../../../../../../environments/environment'; import { environment } from '../../../../../../environments/environment';
import { TranslateModule } from '@ngx-translate/core';
let component: ClaimedDeclinedSearchResultListElementComponent; let component: ClaimedDeclinedSearchResultListElementComponent;
let fixture: ComponentFixture<ClaimedDeclinedSearchResultListElementComponent>; let fixture: ComponentFixture<ClaimedDeclinedSearchResultListElementComponent>;
@@ -64,7 +65,10 @@ const linkService = getMockLinkService();
describe('ClaimedDeclinedSearchResultListElementComponent', () => { describe('ClaimedDeclinedSearchResultListElementComponent', () => {
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [NoopAnimationsModule], imports: [
TranslateModule.forRoot(),
NoopAnimationsModule,
],
declarations: [ClaimedDeclinedSearchResultListElementComponent, VarDirective], declarations: [ClaimedDeclinedSearchResultListElementComponent, VarDirective],
providers: [ providers: [
{ provide: TruncatableService, useValue: {} }, { provide: TruncatableService, useValue: {} },

View File

@@ -0,0 +1,11 @@
<ng-container *ngVar="(workflowitemRD$ | async)?.payload as workflowitem">
<div class="alert alert-warning w-100" role="alert">
<h4 class="alert-heading mb-0">{{ 'claimed-declined-task-search-result-list-element.title' | translate }}</h4>
<ds-themed-item-list-preview *ngIf="workflowitem"
[item]="(workflowitem?.item | async)?.payload"
[object]="object"
[status]="status"
[showSubmitter]="showSubmitter">
</ds-themed-item-list-preview>
</div>
</ng-container>

View File

@@ -0,0 +1,109 @@
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { of as observableOf } from 'rxjs';
import { ClaimedDeclinedTaskSearchResultListElementComponent } from './claimed-declined-task-search-result-list-element.component';
import { ClaimedDeclinedTaskTaskSearchResult } from '../../../../object-collection/shared/claimed-declined-task-task-search-result.model';
import { Item } from '../../../../../core/shared/item.model';
import { createSuccessfulRemoteDataObject } from '../../../../remote-data.utils';
import { WorkflowItem } from '../../../../../core/submission/models/workflowitem.model';
import { ClaimedTask } from '../../../../../core/tasks/models/claimed-task-object.model';
import { getMockLinkService } from '../../../../mocks/link-service.mock';
import { VarDirective } from '../../../../utils/var.directive';
import { TruncatableService } from '../../../../truncatable/truncatable.service';
import { LinkService } from '../../../../../core/cache/builders/link.service';
import { MyDspaceItemStatusType } from '../../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type';
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
import { DSONameServiceMock } from '../../../../mocks/dso-name.service.mock';
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
import { environment } from '../../../../../../environments/environment';
import { TranslateModule } from '@ngx-translate/core';
let component: ClaimedDeclinedTaskSearchResultListElementComponent;
let fixture: ComponentFixture<ClaimedDeclinedTaskSearchResultListElementComponent>;
const mockResultObject: ClaimedDeclinedTaskTaskSearchResult = new ClaimedDeclinedTaskTaskSearchResult();
mockResultObject.hitHighlights = {};
const item = Object.assign(new Item(), {
bundles: observableOf({}),
metadata: {
'dc.title': [
{
language: 'en_US',
value: 'This is just another title'
}
],
'dc.type': [
{
language: null,
value: 'Article'
}
],
'dc.contributor.author': [
{
language: 'en_US',
value: 'Smith, Donald'
}
],
'dc.date.issued': [
{
language: null,
value: '2015-06-26'
}
]
}
});
const rdItem = createSuccessfulRemoteDataObject(item);
const workflowitem = Object.assign(new WorkflowItem(), { item: observableOf(rdItem) });
const rdWorkflowitem = createSuccessfulRemoteDataObject(workflowitem);
mockResultObject.indexableObject = Object.assign(new ClaimedTask(), { workflowitem: observableOf(rdWorkflowitem) });
const linkService = getMockLinkService();
describe('ClaimedDeclinedTaskSearchResultListElementComponent', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
NoopAnimationsModule,
TranslateModule.forRoot(),
],
declarations: [ClaimedDeclinedTaskSearchResultListElementComponent, VarDirective],
providers: [
{ provide: TruncatableService, useValue: {} },
{ provide: LinkService, useValue: linkService },
{ provide: DSONameService, useClass: DSONameServiceMock },
{ provide: APP_CONFIG, useValue: environment },
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(ClaimedDeclinedTaskSearchResultListElementComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
}));
beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(ClaimedDeclinedTaskSearchResultListElementComponent);
component = fixture.componentInstance;
}));
beforeEach(() => {
component.dso = mockResultObject.indexableObject;
fixture.detectChanges();
});
it('should init workflowitem properly', (done) => {
component.workflowitemRD$.subscribe((workflowitemRD) => {
expect(linkService.resolveLinks).toHaveBeenCalledWith(
component.dso,
jasmine.objectContaining({ name: 'workflowitem' }),
jasmine.objectContaining({ name: 'action' })
);
expect(workflowitemRD.payload).toEqual(workflowitem);
done();
});
});
it('should have properly status', () => {
expect(component.status).toEqual(MyDspaceItemStatusType.DECLINED_TASk);
});
});

View File

@@ -0,0 +1,68 @@
import { Component, Inject, OnInit } from '@angular/core';
import { listableObjectComponent } from '../../../../object-collection/shared/listable-object/listable-object.decorator';
import { ClaimedDeclinedTaskTaskSearchResult } from 'src/app/shared/object-collection/shared/claimed-declined-task-task-search-result.model';
import { ViewMode } from '../../../../../core/shared/view-mode.model';
import { LinkService } from '../../../../../core/cache/builders/link.service';
import { TruncatableService } from '../../../../truncatable/truncatable.service';
import { MyDspaceItemStatusType } from '../../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type';
import { Observable } from 'rxjs';
import { RemoteData } from '../../../../../core/data/remote-data';
import { WorkflowItem } from '../../../../../core/submission/models/workflowitem.model';
import { followLink } from '../../../../utils/follow-link-config.model';
import { SearchResultListElementComponent } from '../../../search-result-list-element/search-result-list-element.component';
import { ClaimedTaskSearchResult } from '../../../../object-collection/shared/claimed-task-search-result.model';
import { ClaimedTask } from '../../../../../core/tasks/models/claimed-task-object.model';
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
import { APP_CONFIG, AppConfig } from '../../../../../../config/app-config.interface';
/**
* This component renders claimed task declined task object for the search result in the list view.
*/
@Component({
selector: 'ds-claimed-declined-task-search-result-list-element',
styleUrls: ['../../../search-result-list-element/search-result-list-element.component.scss'],
templateUrl: './claimed-declined-task-search-result-list-element.component.html'
})
@listableObjectComponent(ClaimedDeclinedTaskTaskSearchResult, ViewMode.ListElement)
export class ClaimedDeclinedTaskSearchResultListElementComponent extends SearchResultListElementComponent<ClaimedTaskSearchResult, ClaimedTask> implements OnInit {
/**
* A boolean representing if to show submitter information
*/
public showSubmitter = true;
/**
* Represent item's status
*/
public status = MyDspaceItemStatusType.DECLINED_TASk;
/**
* The workflowitem object that belonging to the result object
*/
public workflowitemRD$: Observable<RemoteData<WorkflowItem>>;
public constructor(
protected linkService: LinkService,
protected truncatableService: TruncatableService,
protected dsoNameService: DSONameService,
@Inject(APP_CONFIG) protected appConfig: AppConfig,
) {
super(truncatableService, dsoNameService, appConfig);
}
/**
* Initialize all instance variables
*/
ngOnInit() {
super.ngOnInit();
this.linkService.resolveLinks(this.dso,
followLink('workflowitem',
{ useCachedVersionIfAvailable: false },
followLink('item'),
followLink('submitter')
),
followLink('action'));
this.workflowitemRD$ = this.dso.workflowitem as Observable<RemoteData<WorkflowItem>>;
}
}

View File

@@ -0,0 +1,12 @@
import { Observable, of as observableOf } from 'rxjs';
/**
* Stub service for {@link RequestService}.
*/
export class RequestServiceStub {
removeByHrefSubstring(_href: string): Observable<boolean> {
return observableOf(true);
}
}

View File

@@ -7,15 +7,15 @@
</p> </p>
<form (ngSubmit)="performAction()" *ngIf="ratingForm" [formGroup]="ratingForm"> <form (ngSubmit)="performAction()" *ngIf="ratingForm" [formGroup]="ratingForm">
<div *ngVar="ratingForm.get('review').touched && !ratingForm.get('review').valid as invalid" class="form-group"> <div class="form-group">
<label class="control-label"> <label class="control-label">
<span>{{ 'advanced-workflow-action.rating.form.review.label' | translate }}</span> <span>{{ 'advanced-workflow-action.rating.form.review.label' | translate }}</span>
<span *ngIf="advancedInfo?.descriptionRequired">*</span> <span *ngIf="advancedInfo?.descriptionRequired">*</span>
</label> </label>
<textarea [ngClass]="invalid ? 'is-invalid' : ''" [required]="advancedInfo?.descriptionRequired" <textarea [ngClass]="{ 'is-invalid' : isInvalid('review') }"
class="form-control" formControlName="review"> [required]="advancedInfo?.descriptionRequired" class="form-control" formControlName="review">
</textarea> </textarea>
<small *ngIf="invalid" class="invalid-feedback d-block"> <small *ngIf="isInvalid('review')" class="invalid-feedback d-block">
{{ 'advanced-workflow-action.rating.form.review.error' | translate }} {{ 'advanced-workflow-action.rating.form.review.error' | translate }}
</small> </small>
</div> </div>
@@ -24,8 +24,12 @@
<label class="control-label"> <label class="control-label">
{{ 'advanced-workflow-action.rating.form.rating.label' | translate }}* {{ 'advanced-workflow-action.rating.form.rating.label' | translate }}*
</label> </label>
<rating [max]="advancedInfo?.maxValue" class="d-block" formControlName="rating"> <rating [max]="advancedInfo?.maxValue" [ngClass]="{ 'text-danger': isInvalid('rating') }"
class="d-block" formControlName="rating">
</rating> </rating>
<small *ngIf="isInvalid('rating')" class="invalid-feedback d-block">
{{ 'advanced-workflow-action.rating.form.rating.error' | translate }}
</small>
</div> </div>
</form> </form>

View File

@@ -26,6 +26,8 @@ import { Item } from '../../../core/shared/item.model';
import { NO_ERRORS_SCHEMA } from '@angular/core'; import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response'; import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response';
import { RatingAdvancedWorkflowInfo } from '../../../core/tasks/models/rating-advanced-workflow-info.model'; import { RatingAdvancedWorkflowInfo } from '../../../core/tasks/models/rating-advanced-workflow-info.model';
import { RequestService } from '../../../core/data/request.service';
import { RequestServiceStub } from '../../../shared/testing/request-service.stub';
const claimedTaskId = '2'; const claimedTaskId = '2';
const workflowId = '1'; const workflowId = '1';
@@ -80,6 +82,7 @@ describe('AdvancedWorkflowActionRatingComponent', () => {
{ provide: Router, useValue: new RouterStub() }, { provide: Router, useValue: new RouterStub() },
{ provide: WorkflowActionDataService, useValue: workflowActionDataService }, { provide: WorkflowActionDataService, useValue: workflowActionDataService },
{ provide: WorkflowItemDataService, useValue: workflowItemDataService }, { provide: WorkflowItemDataService, useValue: workflowItemDataService },
{ provide: RequestService, useClass: RequestServiceStub },
], ],
schemas: [NO_ERRORS_SCHEMA], schemas: [NO_ERRORS_SCHEMA],
}).compileComponents(); }).compileComponents();

View File

@@ -3,11 +3,9 @@ import {
rendersAdvancedWorkflowTaskOption rendersAdvancedWorkflowTaskOption
} from '../../../shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator'; } from '../../../shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator';
import { AdvancedWorkflowActionComponent } from '../advanced-workflow-action/advanced-workflow-action.component'; import { AdvancedWorkflowActionComponent } from '../advanced-workflow-action/advanced-workflow-action.component';
import { FormGroup, FormControl } from '@angular/forms'; import { FormGroup, FormControl, Validators } 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 { RatingAdvancedWorkflowInfo } from '../../../core/tasks/models/rating-advanced-workflow-info.model';
RatingAdvancedWorkflowInfo
} from '../../../core/tasks/models/rating-advanced-workflow-info.model';
export const ADVANCED_WORKFLOW_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';
@@ -30,7 +28,7 @@ export class AdvancedWorkflowActionRatingComponent extends AdvancedWorkflowActio
super.ngOnInit(); super.ngOnInit();
this.ratingForm = new FormGroup({ this.ratingForm = new FormGroup({
review: new FormControl(''), review: new FormControl(''),
rating: new FormControl(0), rating: new FormControl(0, Validators.min(1)),
}); });
} }
@@ -69,4 +67,13 @@ export class AdvancedWorkflowActionRatingComponent extends AdvancedWorkflowActio
return workflowAction ? (workflowAction.advancedInfo[0] as RatingAdvancedWorkflowInfo) : null; return workflowAction ? (workflowAction.advancedInfo[0] as RatingAdvancedWorkflowInfo) : null;
} }
/**
* Returns whether the field is valid or not.
*
* @param formControlName The input field
*/
isInvalid(formControlName: string): boolean {
return this.ratingForm.get(formControlName).touched && !this.ratingForm.get(formControlName).valid;
}
} }

View File

@@ -23,6 +23,8 @@ import { Item } from '../../../core/shared/item.model';
import { EPersonMock, EPersonMock2 } from '../../../shared/testing/eperson.mock'; import { EPersonMock, EPersonMock2 } from '../../../shared/testing/eperson.mock';
import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response'; import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response';
import { NO_ERRORS_SCHEMA } from '@angular/core'; import { NO_ERRORS_SCHEMA } from '@angular/core';
import { RequestService } from '../../../core/data/request.service';
import { RequestServiceStub } from '../../../shared/testing/request-service.stub';
const claimedTaskId = '2'; const claimedTaskId = '2';
const workflowId = '1'; const workflowId = '1';
@@ -73,6 +75,7 @@ describe('AdvancedWorkflowActionSelectReviewerComponent', () => {
{ provide: RouteService, useValue: routeServiceStub }, { provide: RouteService, useValue: routeServiceStub },
{ provide: WorkflowActionDataService, useValue: workflowActionDataService }, { provide: WorkflowActionDataService, useValue: workflowActionDataService },
{ provide: WorkflowItemDataService, useValue: workflowItemDataService }, { provide: WorkflowItemDataService, useValue: workflowItemDataService },
{ provide: RequestService, useClass: RequestServiceStub },
], ],
schemas: [NO_ERRORS_SCHEMA], schemas: [NO_ERRORS_SCHEMA],
}).compileComponents(); }).compileComponents();

View File

@@ -19,6 +19,7 @@ import { NotificationsService } from '../../../shared/notifications/notification
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { WorkflowActionDataService } from '../../../core/data/workflow-action-data.service'; import { WorkflowActionDataService } from '../../../core/data/workflow-action-data.service';
import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service'; import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service';
import { RequestService } from 'src/app/core/data/request.service';
export const ADVANCED_WORKFLOW_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';
@@ -59,8 +60,9 @@ export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkf
protected translationService: TranslateService, protected translationService: TranslateService,
protected workflowActionService: WorkflowActionDataService, protected workflowActionService: WorkflowActionDataService,
protected claimedTaskDataService: ClaimedTaskDataService, protected claimedTaskDataService: ClaimedTaskDataService,
protected requestService: RequestService,
) { ) {
super(route, workflowItemService, router, routeService, notificationsService, translationService, workflowActionService, claimedTaskDataService); super(route, workflowItemService, router, routeService, notificationsService, translationService, workflowActionService, claimedTaskDataService, requestService);
} }
ngOnDestroy(): void { ngOnDestroy(): void {
@@ -130,4 +132,12 @@ export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkf
}; };
} }
/**
* Hardcoded the previous page url because the {@link ReviewersListComponent} changes the previous route when
* switching between the different pages
*/
previousPage(): void {
void this.router.navigate(['/mydspace'], { queryParams: { configuration: 'workflow' } });
}
} }

View File

@@ -18,6 +18,8 @@ import { TranslateModule } from '@ngx-translate/core';
import { WorkflowActionDataServiceStub } from '../../../shared/testing/workflow-action-data-service.stub'; import { WorkflowActionDataServiceStub } from '../../../shared/testing/workflow-action-data-service.stub';
import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response'; import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response';
import { WorkflowItemDataServiceStub } from '../../../shared/testing/workflow-item-data-service.stub'; import { WorkflowItemDataServiceStub } from '../../../shared/testing/workflow-item-data-service.stub';
import { RequestService } from '../../../core/data/request.service';
import { RequestServiceStub } from '../../../shared/testing/request-service.stub';
const workflowId = '1'; const workflowId = '1';
@@ -65,6 +67,7 @@ describe('AdvancedWorkflowActionComponent', () => {
{ provide: RouteService, useValue: routeServiceStub }, { provide: RouteService, useValue: routeServiceStub },
{ provide: WorkflowActionDataService, useValue: workflowActionDataService }, { provide: WorkflowActionDataService, useValue: workflowActionDataService },
{ provide: WorkflowItemDataService, useValue: workflowItemDataService }, { provide: WorkflowItemDataService, useValue: workflowItemDataService },
{ provide: RequestService, useClass: RequestServiceStub },
], ],
}).compileComponents(); }).compileComponents();
}); });

View File

@@ -12,12 +12,13 @@ import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operato
import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service'; import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service';
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';
import { RequestService } from '../../../core/data/request.service';
/** /**
* Abstract component for rendering an advanced claimed task's workflow page * Abstract component for rendering an advanced claimed task's workflow page
* To create a child-component for a new option: * To create a child-component for a new option:
* - Set the "getType()" of the component * - Set the "{@link getType}()" of the component
* - Implement the createBody, should always contain at least the ADVANCED_WORKFLOW_TASK_OPTION * - Implement the {@link createBody}, should always contain at least the ADVANCED_WORKFLOW_TASK_OPTION_*
*/ */
@Component({ @Component({
selector: 'ds-advanced-workflow-action', selector: 'ds-advanced-workflow-action',
@@ -36,8 +37,9 @@ export abstract class AdvancedWorkflowActionComponent extends WorkflowItemAction
protected translationService: TranslateService, protected translationService: TranslateService,
protected workflowActionService: WorkflowActionDataService, protected workflowActionService: WorkflowActionDataService,
protected claimedTaskDataService: ClaimedTaskDataService, protected claimedTaskDataService: ClaimedTaskDataService,
protected requestService: RequestService,
) { ) {
super(route, workflowItemService, router, routeService, notificationsService, translationService); super(route, workflowItemService, router, routeService, notificationsService, translationService, requestService);
} }
ngOnInit(): void { ngOnInit(): void {

View File

@@ -16,6 +16,8 @@ import { TranslateLoaderMock } from '../shared/mocks/translate-loader.mock';
import { ActivatedRouteStub } from '../shared/testing/active-router.stub'; import { ActivatedRouteStub } from '../shared/testing/active-router.stub';
import { RouterStub } from '../shared/testing/router.stub'; import { RouterStub } from '../shared/testing/router.stub';
import { NotificationsServiceStub } from '../shared/testing/notifications-service.stub'; import { NotificationsServiceStub } from '../shared/testing/notifications-service.stub';
import { RequestService } from '../core/data/request.service';
import { RequestServiceStub } from '../shared/testing/request-service.stub';
const type = 'testType'; const type = 'testType';
describe('WorkflowItemActionPageComponent', () => { describe('WorkflowItemActionPageComponent', () => {
@@ -52,6 +54,7 @@ describe('WorkflowItemActionPageComponent', () => {
{ provide: RouteService, useValue: {} }, { provide: RouteService, useValue: {} },
{ provide: NotificationsService, useClass: NotificationsServiceStub }, { provide: NotificationsService, useClass: NotificationsServiceStub },
{ provide: WorkflowItemDataService, useValue: wfiService }, { provide: WorkflowItemDataService, useValue: wfiService },
{ provide: RequestService, useClass: RequestServiceStub },
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}) })
@@ -110,8 +113,10 @@ class TestComponent extends WorkflowItemActionPageComponent {
protected router: Router, protected router: Router,
protected routeService: RouteService, protected routeService: RouteService,
protected notificationsService: NotificationsService, protected notificationsService: NotificationsService,
protected translationService: TranslateService) { protected translationService: TranslateService,
super(route, workflowItemService, router, routeService, notificationsService, translationService); protected requestService: RequestService,
) {
super(route, workflowItemService, router, routeService, notificationsService, translationService, requestService);
} }
getType(): string { getType(): string {

View File

@@ -1,16 +1,17 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable, forkJoin } from 'rxjs';
import { map, switchMap, take } from 'rxjs/operators'; import { map, switchMap, take } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { WorkflowItem } from '../core/submission/models/workflowitem.model'; import { WorkflowItem } from '../core/submission/models/workflowitem.model';
import { Item } from '../core/shared/item.model'; import { Item } from '../core/shared/item.model';
import { ActivatedRoute, Data, Router } from '@angular/router'; import { ActivatedRoute, Data, Router, Params } from '@angular/router';
import { WorkflowItemDataService } from '../core/submission/workflowitem-data.service'; import { WorkflowItemDataService } from '../core/submission/workflowitem-data.service';
import { RouteService } from '../core/services/route.service'; import { RouteService } from '../core/services/route.service';
import { NotificationsService } from '../shared/notifications/notifications.service'; import { NotificationsService } from '../shared/notifications/notifications.service';
import { RemoteData } from '../core/data/remote-data'; import { RemoteData } from '../core/data/remote-data';
import { getAllSucceededRemoteData, getRemoteDataPayload } from '../core/shared/operators'; import { getAllSucceededRemoteData, getRemoteDataPayload } from '../core/shared/operators';
import { isEmpty } from '../shared/empty.util'; import { isEmpty } from '../shared/empty.util';
import { RequestService } from '../core/data/request.service';
/** /**
* Abstract component representing a page to perform an action on a workflow item * Abstract component representing a page to perform an action on a workflow item
@@ -29,7 +30,9 @@ export abstract class WorkflowItemActionPageComponent implements OnInit {
protected router: Router, protected router: Router,
protected routeService: RouteService, protected routeService: RouteService,
protected notificationsService: NotificationsService, protected notificationsService: NotificationsService,
protected translationService: TranslateService) { protected translationService: TranslateService,
protected requestService: RequestService,
) {
} }
/** /**
@@ -45,9 +48,9 @@ export abstract class WorkflowItemActionPageComponent implements OnInit {
* Performs the action and shows a notification based on the outcome of the action * Performs the action and shows a notification based on the outcome of the action
*/ */
performAction() { performAction() {
this.wfi$.pipe( forkJoin([this.wfi$, this.requestService.removeByHrefSubstring('/discover')]).pipe(
take(1), take(1),
switchMap((wfi: WorkflowItem) => this.sendRequest(wfi.id)) switchMap(([wfi]) => this.sendRequest(wfi.id))
).subscribe((successful: boolean) => { ).subscribe((successful: boolean) => {
if (successful) { if (successful) {
const title = this.translationService.get('workflow-item.' + this.type + '.notification.success.title'); const title = this.translationService.get('workflow-item.' + this.type + '.notification.success.title');
@@ -72,7 +75,13 @@ export abstract class WorkflowItemActionPageComponent implements OnInit {
if (isEmpty(url)) { if (isEmpty(url)) {
url = '/mydspace'; url = '/mydspace';
} }
this.router.navigateByUrl(url); const params: Params = {};
if (url.split('?').length > 1) {
for (const param of url.split('?')[1].split('&')) {
params[param.split('=')[0]] = param.split('=')[1];
}
}
void this.router.navigate([url.split('?')[0]], { queryParams: params });
} }
); );
} }

View File

@@ -27,7 +27,7 @@ export class WorkflowItemDeleteComponent extends WorkflowItemActionPageComponent
protected notificationsService: NotificationsService, protected notificationsService: NotificationsService,
protected translationService: TranslateService, protected translationService: TranslateService,
protected requestService: RequestService) { protected requestService: RequestService) {
super(route, workflowItemService, router, routeService, notificationsService, translationService); super(route, workflowItemService, router, routeService, notificationsService, translationService, requestService);
} }
/** /**
@@ -42,7 +42,6 @@ export class WorkflowItemDeleteComponent extends WorkflowItemActionPageComponent
* @param id The id of the WorkflowItem * @param id The id of the WorkflowItem
*/ */
sendRequest(id: string): Observable<boolean> { sendRequest(id: string): Observable<boolean> {
this.requestService.removeByHrefSubstring('/discover');
return this.workflowItemService.delete(id).pipe( return this.workflowItemService.delete(id).pipe(
getFirstCompletedRemoteData(), getFirstCompletedRemoteData(),
map((response: RemoteData<NoContent>) => response.hasSucceeded) map((response: RemoteData<NoContent>) => response.hasSucceeded)

View File

@@ -23,7 +23,7 @@ export class WorkflowItemSendBackComponent extends WorkflowItemActionPageCompone
protected notificationsService: NotificationsService, protected notificationsService: NotificationsService,
protected translationService: TranslateService, protected translationService: TranslateService,
protected requestService: RequestService) { protected requestService: RequestService) {
super(route, workflowItemService, router, routeService, notificationsService, translationService); super(route, workflowItemService, router, routeService, notificationsService, translationService, requestService);
} }
/** /**
@@ -38,7 +38,6 @@ export class WorkflowItemSendBackComponent extends WorkflowItemActionPageCompone
* @param id The id of the WorkflowItem * @param id The id of the WorkflowItem
*/ */
sendRequest(id: string): Observable<boolean> { sendRequest(id: string): Observable<boolean> {
this.requestService.removeByHrefSubstring('/discover');
return this.workflowItemService.sendBack(id); return this.workflowItemService.sendBack(id);
} }
} }

View File

@@ -582,6 +582,8 @@
"advanced-workflow-action.rating.form.rating.label": "Rating", "advanced-workflow-action.rating.form.rating.label": "Rating",
"advanced-workflow-action.rating.form.rating.error": "You must rate the item",
"advanced-workflow-action.rating.form.review.label": "Review", "advanced-workflow-action.rating.form.review.label": "Review",
"advanced-workflow-action.rating.form.review.error": "You must enter a review to submit this rating", "advanced-workflow-action.rating.form.review.error": "You must enter a review to submit this rating",
@@ -845,6 +847,12 @@
"chips.remove": "Remove chip", "chips.remove": "Remove chip",
"claimed-approved-search-result-list-element.title": "Approved",
"claimed-declined-search-result-list-element.title": "Rejected, sent back to submitter",
"claimed-declined-task-search-result-list-element.title": "Declined, sent back to Review Manager's workflow",
"collection.create.head": "Create a Collection", "collection.create.head": "Create a Collection",

View File

@@ -231,7 +231,6 @@ const DECLARATIONS = [
], ],
declarations: DECLARATIONS, declarations: DECLARATIONS,
exports: [ exports: [
FullItemPageComponent,
CommunityPageSubCollectionListComponent CommunityPageSubCollectionListComponent
] ]
}) })