diff --git a/src/app/access-control/access-control.module.ts b/src/app/access-control/access-control.module.ts index afb92a9111..47a971a882 100644 --- a/src/app/access-control/access-control.module.ts +++ b/src/app/access-control/access-control.module.ts @@ -27,7 +27,10 @@ export const ValidateEmailErrorStateMatcher: DynamicErrorMessagesMatcher = SharedModule, RouterModule, AccessControlRoutingModule, - FormModule + FormModule, + ], + exports: [ + MembersListComponent, ], declarations: [ EPeopleRegistryComponent, @@ -35,7 +38,7 @@ export const ValidateEmailErrorStateMatcher: DynamicErrorMessagesMatcher = GroupsRegistryComponent, GroupFormComponent, SubgroupsListComponent, - MembersListComponent + MembersListComponent, ], providers: [ { diff --git a/src/app/access-control/group-registry/group-form/members-list/members-list.component.html b/src/app/access-control/group-registry/group-form/members-list/members-list.component.html index 8b0ae35bd4..282ee89674 100644 --- a/src/app/access-control/group-registry/group-form/members-list/members-list.component.html +++ b/src/app/access-control/group-registry/group-form/members-list/members-list.component.html @@ -65,18 +65,20 @@
- -
@@ -123,10 +125,19 @@
- +
diff --git a/src/app/access-control/group-registry/group-form/members-list/members-list.component.spec.ts b/src/app/access-control/group-registry/group-form/members-list/members-list.component.spec.ts index 8d0ddf0a85..b7536177cd 100644 --- a/src/app/access-control/group-registry/group-form/members-list/members-list.component.spec.ts +++ b/src/app/access-control/group-registry/group-form/members-list/members-list.component.spec.ts @@ -149,6 +149,7 @@ describe('MembersListComponent', () => { fixture.destroy(); flush(); component = null; + fixture.debugElement.nativeElement.remove(); })); it('should create MembersListComponent', inject([MembersListComponent], (comp: MembersListComponent) => { diff --git a/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts b/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts index 169d009d63..58d252f0b4 100644 --- a/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts +++ b/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts @@ -11,7 +11,7 @@ import { ObservedValueOf, } from 'rxjs'; import { defaultIfEmpty, map, mergeMap, switchMap, take } from 'rxjs/operators'; -import {buildPaginatedList, PaginatedList} from '../../../../core/data/paginated-list.model'; +import { buildPaginatedList, PaginatedList } from '../../../../core/data/paginated-list.model'; import { RemoteData } from '../../../../core/data/remote-data'; import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; import { GroupDataService } from '../../../../core/eperson/group-data.service'; @@ -19,11 +19,13 @@ import { EPerson } from '../../../../core/eperson/models/eperson.model'; import { Group } from '../../../../core/eperson/models/group.model'; import { getFirstSucceededRemoteData, - getFirstCompletedRemoteData, getAllCompletedRemoteData, getRemoteDataPayload + getFirstCompletedRemoteData, + getAllCompletedRemoteData, + getRemoteDataPayload } from '../../../../core/shared/operators'; import { NotificationsService } from '../../../../shared/notifications/notifications.service'; import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model'; -import {EpersonDtoModel} from '../../../../core/eperson/models/eperson-dto.model'; +import { EpersonDtoModel } from '../../../../core/eperson/models/eperson-dto.model'; import { PaginationService } from '../../../../core/pagination/pagination.service'; /** @@ -35,6 +37,35 @@ enum SubKey { SearchResultsDTO, } +/** + * The layout config of the buttons in the last column + */ +export interface EPersonActionConfig { + /** + * The css classes that should be added to the button + */ + css?: string; + /** + * Whether the button should be disabled + */ + disabled: boolean; + /** + * The Font Awesome icon that should be used + */ + 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 { + add: EPersonActionConfig; + remove: EPersonActionConfig; +} + @Component({ selector: 'ds-members-list', templateUrl: './members-list.component.html' @@ -47,6 +78,20 @@ export class MembersListComponent implements OnInit, OnDestroy { @Input() messagePrefix: string; + @Input() + actionConfig: EPersonListActionConfig = { + add: { + css: 'btn-outline-primary', + disabled: false, + icon: 'fas fa-plus fa-fw', + }, + remove: { + css: 'btn-outline-danger', + disabled: false, + icon: 'fas fa-trash-alt fa-fw' + }, + }; + /** * EPeople being displayed in search result, initially all members, after search result of search */ @@ -91,21 +136,20 @@ export class MembersListComponent implements OnInit, OnDestroy { // current active group being edited groupBeingEdited: Group; - paginationSub: Subscription; - - - constructor(private groupDataService: GroupDataService, - public ePersonDataService: EPersonDataService, - private translateService: TranslateService, - private notificationsService: NotificationsService, - private formBuilder: FormBuilder, - private paginationService: PaginationService, - private router: Router) { + constructor( + protected groupDataService: GroupDataService, + public ePersonDataService: EPersonDataService, + protected translateService: TranslateService, + protected notificationsService: NotificationsService, + protected formBuilder: FormBuilder, + protected paginationService: PaginationService, + private router: Router + ) { this.currentSearchQuery = ''; this.currentSearchScope = 'metadata'; } - ngOnInit() { + ngOnInit(): void { this.searchForm = this.formBuilder.group(({ scope: 'metadata', query: '', @@ -124,7 +168,7 @@ export class MembersListComponent implements OnInit, OnDestroy { * @param page the number of the page to retrieve * @private */ - private retrieveMembers(page: number) { + retrieveMembers(page: number): void { this.unsubFrom(SubKey.MembersDTO); this.subs.set(SubKey.MembersDTO, this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( @@ -135,36 +179,36 @@ export class MembersListComponent implements OnInit, OnDestroy { } ); }), - getAllCompletedRemoteData(), - map((rd: RemoteData) => { - if (rd.hasFailed) { - this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure', {cause: rd.errorMessage})); - } else { - return rd; - } - }), - switchMap((epersonListRD: RemoteData>) => { - const dtos$ = observableCombineLatest([...epersonListRD.payload.page.map((member: EPerson) => { - const dto$: Observable = observableCombineLatest( - this.isMemberOfGroup(member), (isMember: ObservedValueOf>) => { - const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel(); - epersonDtoModel.eperson = member; - epersonDtoModel.memberOfGroup = isMember; - return epersonDtoModel; - }); - return dto$; - })]); - return dtos$.pipe(defaultIfEmpty([]), map((dtos: EpersonDtoModel[]) => { - return buildPaginatedList(epersonListRD.payload.pageInfo, dtos); + getAllCompletedRemoteData(), + map((rd: RemoteData) => { + if (rd.hasFailed) { + this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure', { cause: rd.errorMessage })); + } else { + return rd; + } + }), + switchMap((epersonListRD: RemoteData>) => { + const dtos$ = observableCombineLatest([...epersonListRD.payload.page.map((member: EPerson) => { + const dto$: Observable = observableCombineLatest( + this.isMemberOfGroup(member), (isMember: ObservedValueOf>) => { + const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel(); + epersonDtoModel.eperson = member; + epersonDtoModel.memberOfGroup = isMember; + return epersonDtoModel; + }); + return dto$; + })]); + return dtos$.pipe(defaultIfEmpty([]), map((dtos: EpersonDtoModel[]) => { + return buildPaginatedList(epersonListRD.payload.pageInfo, dtos); + })); + })) + .subscribe((paginatedListOfDTOs: PaginatedList) => { + this.ePeopleMembersOfGroupDtos.next(paginatedListOfDTOs); })); - })) - .subscribe((paginatedListOfDTOs: PaginatedList) => { - this.ePeopleMembersOfGroupDtos.next(paginatedListOfDTOs); - })); } /** - * Whether or not the given ePerson is a member of the group currently being edited + * Whether the given ePerson is a member of the group currently being edited * @param possibleMember EPerson that is a possible member (being tested) of the group currently being edited */ isMemberOfGroup(possibleMember: EPerson): Observable { @@ -193,7 +237,7 @@ export class MembersListComponent implements OnInit, OnDestroy { * @param key The key of the subscription to unsubscribe from * @private */ - private unsubFrom(key: SubKey) { + protected unsubFrom(key: SubKey) { if (this.subs.has(key)) { this.subs.get(key).unsubscribe(); this.subs.delete(key); @@ -267,7 +311,7 @@ export class MembersListComponent implements OnInit, OnDestroy { getAllCompletedRemoteData(), map((rd: RemoteData) => { if (rd.hasFailed) { - this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure', {cause: rd.errorMessage})); + this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure', { cause: rd.errorMessage })); } else { return rd; } diff --git a/src/app/collection-page/edit-collection-page/collection-roles/collection-roles.component.ts b/src/app/collection-page/edit-collection-page/collection-roles/collection-roles.component.ts index a6c37cbc45..0177cc3a38 100644 --- a/src/app/collection-page/edit-collection-page/collection-roles/collection-roles.component.ts +++ b/src/app/collection-page/edit-collection-page/collection-roles/collection-roles.component.ts @@ -6,6 +6,7 @@ import { RemoteData } from '../../../core/data/remote-data'; import { Collection } from '../../../core/shared/collection.model'; import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../../../core/shared/operators'; import { HALLink } from '../../../core/shared/hal-link.model'; +import { hasValue } from '../../../shared/empty.util'; /** * Component for managing a collection's roles @@ -45,25 +46,31 @@ export class CollectionRolesComponent implements OnInit { ); this.comcolRoles$ = this.collection$.pipe( - map((collection) => [ - { - name: 'collection-admin', - href: collection._links.adminGroup.href, - }, - { - name: 'submitters', - href: collection._links.submittersGroup.href, - }, - { - name: 'item_read', - href: collection._links.itemReadGroup.href, - }, - { - name: 'bitstream_read', - href: collection._links.bitstreamReadGroup.href, - }, - ...collection._links.workflowGroups, - ]), + map((collection) => { + let workflowGroups: HALLink[] | HALLink = hasValue(collection._links.workflowGroups) ? collection._links.workflowGroups : []; + if (!Array.isArray(workflowGroups)) { + workflowGroups = [workflowGroups]; + } + return [ + { + name: 'collection-admin', + href: collection._links.adminGroup.href, + }, + { + name: 'submitters', + href: collection._links.submittersGroup.href, + }, + { + name: 'item_read', + href: collection._links.itemReadGroup.href, + }, + { + name: 'bitstream_read', + href: collection._links.bitstreamReadGroup.href, + }, + ...workflowGroups, + ]; + }), ); } } diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 0aed8c0578..1a5c1df2dc 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -157,6 +157,9 @@ import { SequenceService } from './shared/sequence.service'; import { CoreState } from './core-state.model'; import { GroupDataService } from './eperson/group-data.service'; 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 { AccessStatusDataService } from './data/access-status-data.service'; import { LinkHeadService } from './services/link-head.service'; @@ -341,6 +344,9 @@ export const models = Version, VersionHistory, WorkflowAction, + AdvancedWorkflowInfo, + RatingAdvancedWorkflowInfo, + SelectReviewerAdvancedWorkflowInfo, TemplateItem, Feature, Authorization, diff --git a/src/app/core/data/request.service.spec.ts b/src/app/core/data/request.service.spec.ts index fe35d840d7..108a588881 100644 --- a/src/app/core/data/request.service.spec.ts +++ b/src/app/core/data/request.service.spec.ts @@ -594,6 +594,19 @@ describe('RequestService', () => { 'property1=multiple%0Alines%0Ato%0Asend&property2=sp%26ci%40l%20characters&sp%26ci%40l-chars%20in%20prop=test123' ); }); + + it('should properly encode the body with an array', () => { + const body = { + 'property1': 'multiple\nlines\nto\nsend', + 'property2': 'sp&ci@l characters', + 'sp&ci@l-chars in prop': 'test123', + 'arrayParam': ['arrayValue1', 'arrayValue2'], + }; + const queryParams = service.uriEncodeBody(body); + expect(queryParams).toEqual( + 'property1=multiple%0Alines%0Ato%0Asend&property2=sp%26ci%40l%20characters&sp%26ci%40l-chars%20in%20prop=test123&arrayParam=arrayValue1&arrayParam=arrayValue2' + ); + }); }); describe('setStaleByUUID', () => { diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index 16dc14dac4..94a6020975 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -255,8 +255,8 @@ export class RequestService { /** * Convert request Payload to a URL-encoded string * - * e.g. uriEncodeBody({param: value, param1: value1}) - * returns: param=value¶m1=value1 + * e.g. uriEncodeBody({param: value, param1: value1, param2: [value3, value4]}) + * returns: param=value¶m1=value1¶m2=value3¶m2=value4 * * @param body * The request Payload to convert @@ -267,11 +267,19 @@ export class RequestService { let queryParams = ''; if (isNotEmpty(body) && typeof body === 'object') { Object.keys(body) - .forEach((param) => { + .forEach((param: string) => { const encodedParam = encodeURIComponent(param); - const encodedBody = encodeURIComponent(body[param]); - const paramValue = `${encodedParam}=${encodedBody}`; - queryParams = isEmpty(queryParams) ? queryParams.concat(paramValue) : queryParams.concat('&', paramValue); + if (Array.isArray(body[param])) { + for (const element of body[param]) { + const encodedBody = encodeURIComponent(element); + const paramValue = `${encodedParam}=${encodedBody}`; + queryParams = isEmpty(queryParams) ? queryParams.concat(paramValue) : queryParams.concat('&', paramValue); + } + } else { + const encodedBody = encodeURIComponent(body[param]); + const paramValue = `${encodedParam}=${encodedBody}`; + queryParams = isEmpty(queryParams) ? queryParams.concat(paramValue) : queryParams.concat('&', paramValue); + } }); } return queryParams; diff --git a/src/app/core/data/workflow-action-data.service.ts b/src/app/core/data/workflow-action-data.service.ts index b2c4f0bd7b..00cd5e2889 100644 --- a/src/app/core/data/workflow-action-data.service.ts +++ b/src/app/core/data/workflow-action-data.service.ts @@ -5,15 +5,15 @@ import { ObjectCacheService } from '../cache/object-cache.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { Injectable } from '@angular/core'; import { WORKFLOW_ACTION } from '../tasks/models/workflow-action-object.resource-type'; -import { BaseDataService } from './base/base-data.service'; import { dataService } from './base/data-service.decorator'; +import { IdentifiableDataService } from './base/identifiable-data.service'; /** * A service responsible for fetching/sending data from/to the REST API on the workflowactions endpoint */ @Injectable() @dataService(WORKFLOW_ACTION) -export class WorkflowActionDataService extends BaseDataService { +export class WorkflowActionDataService extends IdentifiableDataService { protected linkPath = 'workflowactions'; constructor( diff --git a/src/app/core/tasks/models/advanced-workflow-info.model.ts b/src/app/core/tasks/models/advanced-workflow-info.model.ts new file mode 100644 index 0000000000..87991a375c --- /dev/null +++ b/src/app/core/tasks/models/advanced-workflow-info.model.ts @@ -0,0 +1,11 @@ +import { autoserialize } from 'cerialize'; + +/** + * An abstract model class for a {@link AdvancedWorkflowInfo} + */ +export abstract class AdvancedWorkflowInfo { + + @autoserialize + id: string; + +} diff --git a/src/app/core/tasks/models/advanced-workflow-info.resource-type.ts b/src/app/core/tasks/models/advanced-workflow-info.resource-type.ts new file mode 100644 index 0000000000..4e7793f875 --- /dev/null +++ b/src/app/core/tasks/models/advanced-workflow-info.resource-type.ts @@ -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'); diff --git a/src/app/core/tasks/models/rating-advanced-workflow-info.model.ts b/src/app/core/tasks/models/rating-advanced-workflow-info.model.ts new file mode 100644 index 0000000000..b7861d4fe4 --- /dev/null +++ b/src/app/core/tasks/models/rating-advanced-workflow-info.model.ts @@ -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; + +} diff --git a/src/app/core/tasks/models/select-reviewer-advanced-workflow-info.model.ts b/src/app/core/tasks/models/select-reviewer-advanced-workflow-info.model.ts new file mode 100644 index 0000000000..b87770596e --- /dev/null +++ b/src/app/core/tasks/models/select-reviewer-advanced-workflow-info.model.ts @@ -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; + +} diff --git a/src/app/core/tasks/models/workflow-action-object.model.ts b/src/app/core/tasks/models/workflow-action-object.model.ts index 720d817859..0896e6b8f8 100644 --- a/src/app/core/tasks/models/workflow-action-object.model.ts +++ b/src/app/core/tasks/models/workflow-action-object.model.ts @@ -2,6 +2,7 @@ import { inheritSerialization, autoserialize } from 'cerialize'; import { typedObject } from '../../cache/builders/build-decorators'; import { DSpaceObject } from '../../shared/dspace-object.model'; import { WORKFLOW_ACTION } from './workflow-action-object.resource-type'; +import { AdvancedWorkflowInfo } from './advanced-workflow-info.model'; /** * A model class for a WorkflowAction @@ -22,4 +23,23 @@ export class WorkflowAction extends DSpaceObject { */ @autoserialize options: string[]; + + /** + * Whether this action has advanced options + */ + @autoserialize + advanced: boolean; + + /** + * The advanced options that the user can select at this action + */ + @autoserialize + advancedOptions: string[]; + + /** + * The advanced info required by the advanced options + */ + @autoserialize + advancedInfo: AdvancedWorkflowInfo[]; + } diff --git a/src/app/item-page/edit-item-page/modify-item-overview/modify-item-overview.component.html b/src/app/item-page/edit-item-page/modify-item-overview/modify-item-overview.component.html index ce6e01df3d..dbef279d8c 100644 --- a/src/app/item-page/edit-item-page/modify-item-overview/modify-item-overview.component.html +++ b/src/app/item-page/edit-item-page/modify-item-overview/modify-item-overview.component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/app/my-dspace-page/my-dspace-search.module.ts b/src/app/my-dspace-page/my-dspace-search.module.ts index 1ce39991b3..8b03bffea7 100644 --- a/src/app/my-dspace-page/my-dspace-search.module.ts +++ b/src/app/my-dspace-page/my-dspace-search.module.ts @@ -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 { JournalEntitiesModule } from '../entity-groups/journal-entities/journal-entities.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 = [ WorkspaceItemSearchResultListElementComponent, @@ -32,6 +33,7 @@ const ENTRY_COMPONENTS = [ ClaimedSearchResultListElementComponent, ClaimedApprovedSearchResultListElementComponent, ClaimedDeclinedSearchResultListElementComponent, + ClaimedDeclinedTaskSearchResultListElementComponent, PoolSearchResultListElementComponent, ItemSearchResultDetailElementComponent, WorkspaceItemSearchResultDetailElementComponent, diff --git a/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts b/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts new file mode 100644 index 0000000000..cf627fc1ce --- /dev/null +++ b/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts @@ -0,0 +1,86 @@ +import { Component, OnInit, Injector } from '@angular/core'; +import { ClaimedTaskActionsAbstractComponent } from './claimed-task-actions-abstract.component'; +import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators'; +import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; +import { getAdvancedWorkflowRoute } from '../../../../workflowitems-edit-page/workflowitems-edit-page-routing-paths'; +import { Params, Router, ActivatedRoute, NavigationExtras } from '@angular/router'; +import { NotificationsService } from '../../../notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { SearchService } from '../../../../core/shared/search/search.service'; +import { RequestService } from '../../../../core/data/request.service'; + +/** + * Abstract component for rendering an advanced claimed task's action + * To create a child-component for a new option: + * - Set the "{@link option}" and "{@link workflowType}" of the component + * - Add a @{@link rendersWorkflowTaskOption} annotation to your component providing the same enum value + */ +@Component({ + selector: 'ds-advanced-claimed-task-action-abstract', + template: '' +}) +export abstract class AdvancedClaimedTaskActionsAbstractComponent extends ClaimedTaskActionsAbstractComponent implements OnInit { + + /** + * The {@link WorkflowAction} id of the advanced workflow that needs to be opened. + */ + abstract workflowType: string; + + /** + * Route to the workflow's task page + */ + workflowTaskPageRoute: string; + + constructor( + protected injector: Injector, + protected router: Router, + protected notificationsService: NotificationsService, + protected translate: TranslateService, + protected searchService: SearchService, + protected requestService: RequestService, + protected route: ActivatedRoute, + ) { + super(injector, router, notificationsService, translate, searchService, requestService); + } + + ngOnInit(): void { + super.ngOnInit(); + this.initPageRoute(); + } + + /** + * Initialise the route to the advanced workflow page + */ + initPageRoute() { + this.subs.push(this.object.workflowitem.pipe( + getFirstSucceededRemoteDataPayload() + ).subscribe((workflowItem: WorkflowItem) => { + this.workflowTaskPageRoute = getAdvancedWorkflowRoute(workflowItem.id); + })); + } + + /** + * Navigates to the advanced workflow page based on the {@link workflow}. + */ + openAdvancedClaimedTaskTab(): void { + const navigationExtras: NavigationExtras = { + queryParams: this.getQueryParams(), + }; + if (Object.keys(this.route.snapshot.queryParams).length > 0) { + navigationExtras.state = {}; + navigationExtras.state.previousQueryParams = this.route.snapshot.queryParams; + } + void this.router.navigate([this.workflowTaskPageRoute], navigationExtras); + } + + /** + * The {@link Params} that need to be given to the workflow page. + */ + getQueryParams(): Params { + return { + workflow: this.workflowType, + claimedTask: this.object.id, + }; + } + +} diff --git a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html index 34932f47bf..50e3b46dfe 100644 --- a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html +++ b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html @@ -12,12 +12,5 @@ [routerLink]="[getWorkflowItemViewRoute(workflowitem)]"> {{"submission.workflow.generic.view" | translate}} - - - diff --git a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts index dc57105a4e..195a2193e5 100644 --- a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts @@ -14,7 +14,6 @@ import { RequestService } from '../../../core/data/request.service'; import { SearchService } from '../../../core/shared/search/search.service'; import { WorkflowAction } from '../../../core/tasks/models/workflow-action-object.model'; 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 { Item } from '../../../core/shared/item.model'; @@ -48,12 +47,6 @@ export class ClaimedTaskActionsComponent extends MyDSpaceActionsComponent>; - /** - * 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 * diff --git a/src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.html b/src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.html new file mode 100644 index 0000000000..38ed36c0f1 --- /dev/null +++ b/src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.html @@ -0,0 +1,12 @@ + diff --git a/src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.scss b/src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.spec.ts b/src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.spec.ts new file mode 100644 index 0000000000..092e53f3f4 --- /dev/null +++ b/src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.spec.ts @@ -0,0 +1,90 @@ +import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateLoaderMock } from '../../../mocks/translate-loader.mock'; +import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service'; +import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; +import { getMockSearchService } from '../../../mocks/search-service.mock'; +import { getMockRequestService } from '../../../mocks/request.service.mock'; +import { PoolTaskDataService } from '../../../../core/tasks/pool-task-data.service'; +import { NotificationsService } from '../../../notifications/notifications.service'; +import { NotificationsServiceStub } from '../../../testing/notifications-service.stub'; +import { Router } from '@angular/router'; +import { RouterStub } from '../../../testing/router.stub'; +import { SearchService } from '../../../../core/shared/search/search.service'; +import { RequestService } from '../../../../core/data/request.service'; +import { ClaimedTaskActionsDeclineTaskComponent } from './claimed-task-actions-decline-task.component'; +import { ClaimedTaskDataServiceStub } from '../../../testing/claimed-task-data-service.stub'; + +let component: ClaimedTaskActionsDeclineTaskComponent; +let fixture: ComponentFixture; + +const searchService = getMockSearchService(); + +const requestService = getMockRequestService(); + +let mockPoolTaskDataService: PoolTaskDataService; + +describe('ClaimedTaskActionsDeclineTaskComponent', () => { + const object = Object.assign(new ClaimedTask(), { id: 'claimed-task-1' }); + + let claimedTaskService: ClaimedTaskDataServiceStub; + + beforeEach(waitForAsync(() => { + claimedTaskService = new ClaimedTaskDataServiceStub(); + + mockPoolTaskDataService = new PoolTaskDataService(null, null, null, null); + TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }) + ], + providers: [ + { provide: ClaimedTaskDataService, useValue: claimedTaskService }, + { provide: Injector, useValue: {} }, + { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + { provide: Router, useValue: new RouterStub() }, + { provide: SearchService, useValue: searchService }, + { provide: RequestService, useValue: requestService }, + { provide: PoolTaskDataService, useValue: mockPoolTaskDataService }, + ], + declarations: [ClaimedTaskActionsDeclineTaskComponent], + schemas: [NO_ERRORS_SCHEMA] + }).overrideComponent(ClaimedTaskActionsDeclineTaskComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default } + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ClaimedTaskActionsDeclineTaskComponent); + component = fixture.componentInstance; + component.object = object; + spyOn(component, 'initReloadAnchor').and.returnValue(undefined); + fixture.detectChanges(); + }); + + afterEach(() => { + fixture.debugElement.nativeElement.remove(); + }); + + it('should display decline button', () => { + const btn = fixture.debugElement.query(By.css('.declineTaskAction')); + + expect(btn).not.toBeNull(); + }); + + it('should display spin icon when decline is pending', () => { + component.processing$.next(true); + fixture.detectChanges(); + + const span = fixture.debugElement.query(By.css('.declineTaskAction .fa-spin')); + + expect(span).not.toBeNull(); + }); + +}); diff --git a/src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.ts b/src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.ts new file mode 100644 index 0000000000..5afbde7d7d --- /dev/null +++ b/src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.ts @@ -0,0 +1,50 @@ +import { Component, Injector } from '@angular/core'; +import { ClaimedTaskActionsAbstractComponent } from '../abstract/claimed-task-actions-abstract.component'; +import { rendersWorkflowTaskOption } from '../switcher/claimed-task-actions-decorator'; +import { Router } from '@angular/router'; +import { NotificationsService } from '../../../notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { SearchService } from '../../../../core/shared/search/search.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'; + +@rendersWorkflowTaskOption(WORKFLOW_TASK_OPTION_DECLINE_TASK) +@Component({ + selector: 'ds-claimed-task-actions-decline-task', + templateUrl: './claimed-task-actions-decline-task.component.html', + styleUrls: ['./claimed-task-actions-decline-task.component.scss'] +}) +/** + * Component for displaying and processing the decline task action on a workflow task item + */ +export class ClaimedTaskActionsDeclineTaskComponent extends ClaimedTaskActionsAbstractComponent { + + option = WORKFLOW_TASK_OPTION_DECLINE_TASK; + + constructor(protected injector: Injector, + protected router: Router, + protected notificationsService: NotificationsService, + protected translate: TranslateService, + protected searchService: SearchService, + protected requestService: RequestService) { + super(injector, router, notificationsService, translate, searchService, requestService); + } + + reloadObjectExecution(): Observable | DSpaceObject> { + return observableOf(this.object); + } + + convertReloadedObject(dso: DSpaceObject): DSpaceObject { + return Object.assign(new ClaimedDeclinedTaskTaskSearchResult(), dso, { + indexableObject: dso + }); + } + +} diff --git a/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.html b/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.html new file mode 100644 index 0000000000..5b63aa381e --- /dev/null +++ b/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.html @@ -0,0 +1,4 @@ + diff --git a/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.scss b/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.spec.ts b/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.spec.ts new file mode 100644 index 0000000000..bb41fedfb5 --- /dev/null +++ b/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.spec.ts @@ -0,0 +1,98 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { AdvancedClaimedTaskActionRatingComponent } from './advanced-claimed-task-action-rating.component'; +import { By } from '@angular/platform-browser'; +import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; +import { ClaimedTaskDataServiceStub } from '../../../testing/claimed-task-data-service.stub'; +import { NotificationsServiceStub } from '../../../testing/notifications-service.stub'; +import { RouterStub } from '../../../testing/router.stub'; +import { SearchServiceStub } from '../../../testing/search-service.stub'; +import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service'; +import { NotificationsService } from '../../../notifications/notifications.service'; +import { RequestService } from '../../../../core/data/request.service'; +import { Router, ActivatedRoute } from '@angular/router'; +import { SearchService } from '../../../../core/shared/search/search.service'; +import { Location } from '@angular/common'; +import { TranslateModule } from '@ngx-translate/core'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { + ADVANCED_WORKFLOW_ACTION_RATING +} from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component'; +import { of as observableOf } from 'rxjs'; +import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; +import { ActivatedRouteStub } from '../../../testing/active-router.stub'; + +const taskId = 'claimed-task-1'; +const workflowId = 'workflow-1'; + +describe('AdvancedClaimedTaskActionRatingComponent', () => { + const object = Object.assign(new ClaimedTask(), { + id: taskId, + workflowitem: observableOf(Object.assign(new WorkflowItem(), { + id: workflowId, + })), + }); + let component: AdvancedClaimedTaskActionRatingComponent; + let fixture: ComponentFixture; + + let claimedTaskDataService: ClaimedTaskDataServiceStub; + let notificationService: NotificationsServiceStub; + let route: ActivatedRouteStub; + let router: RouterStub; + let searchService: SearchServiceStub; + + beforeEach(async () => { + claimedTaskDataService = new ClaimedTaskDataServiceStub(); + notificationService = new NotificationsServiceStub(); + route = new ActivatedRouteStub(); + router = new RouterStub(); + searchService = new SearchServiceStub(); + + await TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot(), + ], + declarations: [ + AdvancedClaimedTaskActionRatingComponent, + ], + providers: [ + { provide: ActivatedRoute, useValue: route }, + { provide: ClaimedTaskDataService, useValue: claimedTaskDataService }, + { provide: NotificationsService, useValue: notificationService }, + { provide: RequestService, useValue: {} }, + { provide: Router, useValue: router }, + { provide: SearchService, useValue: searchService }, + Location, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AdvancedClaimedTaskActionRatingComponent); + component = fixture.componentInstance; + component.object = object; + fixture.detectChanges(); + }); + + afterEach(() => { + fixture.debugElement.nativeElement.remove(); + }); + + it('should display select reviewer button', () => { + const btn = fixture.debugElement.query(By.css('.ratingReviewerAction')); + + expect(btn).not.toBeNull(); + }); + + it('should navigate to the advanced workflow page when clicked', () => { + component.workflowTaskPageRoute = `/workflowitems/${workflowId}/advanced`; + fixture.debugElement.query(By.css('.ratingReviewerAction')).nativeElement.click(); + + expect(router.navigate).toHaveBeenCalledWith([`/workflowitems/${workflowId}/advanced`], { + queryParams: { + workflow: ADVANCED_WORKFLOW_ACTION_RATING, + claimedTask: taskId, + }, + }); + }); +}); diff --git a/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.ts b/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.ts new file mode 100644 index 0000000000..a1cc81e050 --- /dev/null +++ b/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.ts @@ -0,0 +1,46 @@ +import { Component, Injector } from '@angular/core'; +import { Router, ActivatedRoute } from '@angular/router'; +import { NotificationsService } from '../../../notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { SearchService } from '../../../../core/shared/search/search.service'; +import { RequestService } from '../../../../core/data/request.service'; +import { + AdvancedClaimedTaskActionsAbstractComponent +} from '../abstract/advanced-claimed-task-actions-abstract.component'; +import { + ADVANCED_WORKFLOW_ACTION_RATING, + ADVANCED_WORKFLOW_TASK_OPTION_RATING, +} from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component'; +import { rendersWorkflowTaskOption } from '../switcher/claimed-task-actions-decorator'; + +/** + * Advanced Workflow button that redirect to the {@link AdvancedWorkflowActionRatingComponent} + */ +@rendersWorkflowTaskOption(ADVANCED_WORKFLOW_TASK_OPTION_RATING) +@Component({ + selector: 'ds-advanced-claimed-task-action-rating-reviewer', + templateUrl: './advanced-claimed-task-action-rating.component.html', + styleUrls: ['./advanced-claimed-task-action-rating.component.scss'] +}) +export class AdvancedClaimedTaskActionRatingComponent extends AdvancedClaimedTaskActionsAbstractComponent { + + /** + * This component represents the advanced select option + */ + option = ADVANCED_WORKFLOW_TASK_OPTION_RATING; + + workflowType = ADVANCED_WORKFLOW_ACTION_RATING; + + constructor( + protected injector: Injector, + protected router: Router, + protected notificationsService: NotificationsService, + protected translate: TranslateService, + protected searchService: SearchService, + protected requestService: RequestService, + protected route: ActivatedRoute, + ) { + super(injector, router, notificationsService, translate, searchService, requestService, route); + } + +} diff --git a/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.html b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.html new file mode 100644 index 0000000000..6a45e7e02c --- /dev/null +++ b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.html @@ -0,0 +1,4 @@ + diff --git a/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.scss b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.spec.ts b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.spec.ts new file mode 100644 index 0000000000..81fe423481 --- /dev/null +++ b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.spec.ts @@ -0,0 +1,102 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { + AdvancedClaimedTaskActionSelectReviewerComponent +} from './advanced-claimed-task-action-select-reviewer.component'; +import { Router, ActivatedRoute } from '@angular/router'; +import { RouterStub } from '../../../testing/router.stub'; +import { NotificationsServiceStub } from '../../../testing/notifications-service.stub'; +import { NotificationsService } from '../../../notifications/notifications.service'; +import { TranslateModule } from '@ngx-translate/core'; +import { SearchServiceStub } from '../../../testing/search-service.stub'; +import { SearchService } from '../../../../core/shared/search/search.service'; +import { RequestService } from '../../../../core/data/request.service'; +import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service'; +import { ClaimedTaskDataServiceStub } from '../../../testing/claimed-task-data-service.stub'; +import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; +import { By } from '@angular/platform-browser'; +import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { Location } from '@angular/common'; +import { + ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER +} from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component'; +import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; +import { of as observableOf } from 'rxjs'; +import { ActivatedRouteStub } from '../../../testing/active-router.stub'; + +const taskId = 'claimed-task-1'; +const workflowId = 'workflow-1'; + +describe('AdvancedClaimedTaskActionSelectReviewerComponent', () => { + const object = Object.assign(new ClaimedTask(), { + id: taskId, + workflowitem: observableOf(Object.assign(new WorkflowItem(), { + id: workflowId, + })), + }); + let component: AdvancedClaimedTaskActionSelectReviewerComponent; + let fixture: ComponentFixture; + + let route: ActivatedRouteStub; + let claimedTaskDataService: ClaimedTaskDataServiceStub; + let notificationService: NotificationsServiceStub; + let router: RouterStub; + let searchService: SearchServiceStub; + + beforeEach(async () => { + route = new ActivatedRouteStub(); + claimedTaskDataService = new ClaimedTaskDataServiceStub(); + notificationService = new NotificationsServiceStub(); + router = new RouterStub(); + searchService = new SearchServiceStub(); + + await TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot(), + ], + declarations: [ + AdvancedClaimedTaskActionSelectReviewerComponent, + NgbTooltip, + ], + providers: [ + { provide: ActivatedRoute, useValue: route }, + { provide: ClaimedTaskDataService, useValue: claimedTaskDataService }, + { provide: NotificationsService, useValue: notificationService }, + { provide: RequestService, useValue: {} }, + { provide: Router, useValue: router }, + { provide: SearchService, useValue: searchService }, + Location, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AdvancedClaimedTaskActionSelectReviewerComponent); + component = fixture.componentInstance; + component.object = object; + fixture.detectChanges(); + }); + + afterEach(() => { + fixture.debugElement.nativeElement.remove(); + }); + + it('should display select reviewer button', () => { + const btn = fixture.debugElement.query(By.css('.selectReviewerAction')); + + expect(btn).not.toBeNull(); + }); + + it('should navigate to the advanced workflow page when clicked', () => { + component.workflowTaskPageRoute = `/workflowitems/${workflowId}/advanced`; + fixture.debugElement.query(By.css('.selectReviewerAction')).nativeElement.click(); + + expect(router.navigate).toHaveBeenCalledWith([`/workflowitems/${workflowId}/advanced`], { + queryParams: { + workflow: ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER, + claimedTask: taskId, + }, + }); + }); +}); diff --git a/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts new file mode 100644 index 0000000000..d6217320ba --- /dev/null +++ b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts @@ -0,0 +1,46 @@ +import { Component, Injector } from '@angular/core'; +import { rendersWorkflowTaskOption } from '../switcher/claimed-task-actions-decorator'; +import { + AdvancedClaimedTaskActionsAbstractComponent +} from '../abstract/advanced-claimed-task-actions-abstract.component'; +import { Router, ActivatedRoute } from '@angular/router'; +import { NotificationsService } from '../../../notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { SearchService } from '../../../../core/shared/search/search.service'; +import { RequestService } from '../../../../core/data/request.service'; +import { + ADVANCED_WORKFLOW_ACTION_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'; + +/** + * Advanced Workflow button that redirect to the {@link AdvancedWorkflowActionSelectReviewerComponent} + */ +@rendersWorkflowTaskOption(ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER) +@Component({ + selector: 'ds-advanced-claimed-task-action-select-reviewer', + templateUrl: './advanced-claimed-task-action-select-reviewer.component.html', + styleUrls: ['./advanced-claimed-task-action-select-reviewer.component.scss'] +}) +export class AdvancedClaimedTaskActionSelectReviewerComponent extends AdvancedClaimedTaskActionsAbstractComponent { + + /** + * This component represents the advanced select option + */ + option = ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER; + + workflowType = ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER; + + constructor( + protected injector: Injector, + protected router: Router, + protected notificationsService: NotificationsService, + protected translate: TranslateService, + protected searchService: SearchService, + protected requestService: RequestService, + protected route: ActivatedRoute, + ) { + super(injector, router, notificationsService, translate, searchService, requestService, route); + } + +} diff --git a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator.ts b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator.ts index a115c4e5b8..8e4dede9bb 100644 --- a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator.ts +++ b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator.ts @@ -1,23 +1,44 @@ import { hasNoValue } from '../../../empty.util'; -const map = new Map(); +const workflowOptions = new Map(); +const advancedWorkflowOptions = new Map(); /** * Decorator used for rendering ClaimedTaskActions pages by option type */ export function rendersWorkflowTaskOption(option: string) { return function decorator(component: any) { - if (hasNoValue(map.get(option))) { - map.set(option, component); + if (hasNoValue(workflowOptions.get(option))) { + workflowOptions.set(option, component); } else { throw new Error(`There can't be more than one component to render ClaimedTaskActions for option "${option}"`); } }; } +/** + * Decorator used for rendering AdvancedClaimedTaskActions pages by option type + */ +export function rendersAdvancedWorkflowTaskOption(option: string) { + return function decorator(component: any) { + if (hasNoValue(advancedWorkflowOptions.get(option))) { + advancedWorkflowOptions.set(option, component); + } else { + throw new Error(`There can't be more than one component to render AdvancedClaimedTaskActions for option "${option}"`); + } + }; +} + /** * Get the component used for rendering a ClaimedTaskActions page by option type */ export function getComponentByWorkflowTaskOption(option: string) { - return map.get(option); + return workflowOptions.get(option); +} + +/** + * Get the component used for rendering a AdvancedClaimedTaskActions page by option type + */ +export function getAdvancedComponentByWorkflowTaskOption(option: string) { + return advancedWorkflowOptions.get(option); } diff --git a/src/app/shared/object-collection/shared/claimed-declined-task-search-result.model.ts b/src/app/shared/object-collection/shared/claimed-declined-task-search-result.model.ts index eaf63bf6dd..0afb79a274 100644 --- a/src/app/shared/object-collection/shared/claimed-declined-task-search-result.model.ts +++ b/src/app/shared/object-collection/shared/claimed-declined-task-search-result.model.ts @@ -2,7 +2,7 @@ import { ClaimedTask } from '../../../core/tasks/models/claimed-task-object.mode 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 { } diff --git a/src/app/shared/object-collection/shared/claimed-declined-task-task-search-result.model.ts b/src/app/shared/object-collection/shared/claimed-declined-task-task-search-result.model.ts new file mode 100644 index 0000000000..dffb3c1313 --- /dev/null +++ b/src/app/shared/object-collection/shared/claimed-declined-task-task-search-result.model.ts @@ -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 { +} diff --git a/src/app/shared/object-collection/shared/mydspace-item-status/my-dspace-item-status-type.ts b/src/app/shared/object-collection/shared/mydspace-item-status/my-dspace-item-status-type.ts index 5cf4d91b20..ec101f1ee7 100644 --- a/src/app/shared/object-collection/shared/mydspace-item-status/my-dspace-item-status-type.ts +++ b/src/app/shared/object-collection/shared/mydspace-item-status/my-dspace-item-status-type.ts @@ -6,4 +6,5 @@ export enum MyDspaceItemStatusType { ARCHIVED = 'mydspace.status.archived', DECLINED = 'mydspace.status.declined', APPROVED = 'mydspace.status.approved', + DECLINED_TASk = 'mydspace.status.declined-task', } diff --git a/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 b/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 index 544c1ebea8..79e870e130 100644 --- a/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 +++ b/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 @@ -1,6 +1,6 @@
{{'item.edit.modify.overview.field'| translate}}