Skip to content

Commit ae5ac2a

Browse files
[DSC-1924] implement bulk action component
add claim selected add approve selected
1 parent dd06969 commit ae5ac2a

6 files changed

Lines changed: 264 additions & 4 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<div class="w-100 d-flex mt-1 mb-3 align-items-start">
2+
<button
3+
type="button"
4+
class="btn btn-info mr-2"
5+
ngbTooltip="{{'submission.workflow.tasks.pool.claim_selected_help' | translate}}"
6+
[disabled]="(processing$ | async) || !(claimEnabled$ | async)"
7+
(click)="claimAllSelectedTask()"
8+
>
9+
<span>
10+
<i class="fas fa-hand-paper"></i>
11+
{{'submission.workflow.tasks.pool.claim.selected' | translate}}
12+
</span>
13+
</button>
14+
15+
<button
16+
type="button"
17+
class="btn btn-success mr-2"
18+
ngbTooltip="{{'submission.workflow.tasks.claimed.approve_selected_help' | translate}}"
19+
[disabled]="(processing$ | async) || !(claimedTaskActionsEnabled$ | async)"
20+
(click)="submitAllSelectedTask()"
21+
>
22+
<span>
23+
<i class="fa fa-thumbs-up"></i>
24+
{{'submission.workflow.tasks.claimed.approve.selected' | translate}}
25+
</span>
26+
</button>
27+
28+
29+
<span *ngIf="(processing$ | async)">
30+
<i class='fas fa-circle-notch fa-spin'></i>
31+
{{'submission.workflow.tasks.generic.processing' | translate}}
32+
</span>
33+
</div>

src/app/my-dspace-page/my-dspace-new-submission/my-dspace-bulk-action/my-dspace-bulk-action.component.scss

Whitespace-only changes.
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
import { Component, EventEmitter, Input, OnDestroy, OnInit } from '@angular/core';
2+
3+
4+
import { hasValue } from '../../../shared/empty.util';
5+
import { distinctUntilChanged, filter, map, switchMap, take, tap } from 'rxjs/operators';
6+
import { combineLatest, Observable, Subscription } from 'rxjs';
7+
import { PoolTaskSearchResult } from '../../../shared/object-collection/shared/pool-task-search-result.model';
8+
import { SelectableListService } from '../../../shared/object-list/selectable-list/selectable-list.service';
9+
import { PoolTaskDataService } from '../../../core/tasks/pool-task-data.service';
10+
import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service';
11+
import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response';
12+
import { NotificationsService } from '../../../shared/notifications/notifications.service';
13+
import { NotificationOptions } from '../../../shared/notifications/models/notification-options.model';
14+
import { TranslateService } from '@ngx-translate/core';
15+
import { Router } from '@angular/router';
16+
import { SearchService } from '../../../core/shared/search/search.service';
17+
import { RequestService } from '../../../core/data/request.service';
18+
import { ClaimedTaskSearchResult } from '../../../shared/object-collection/shared/claimed-task-search-result.model';
19+
import {
20+
WORKFLOW_TASK_OPTION_APPROVE
21+
} from '../../../shared/mydspace-actions/claimed-task/approve/claimed-task-actions-approve.component';
22+
23+
24+
/**
25+
* This gives the option to perform bulk action on workflow items
26+
*/
27+
@Component({
28+
selector: 'ds-my-dspace-bulk-action',
29+
templateUrl: './my-dspace-bulk-action.component.html'
30+
})
31+
export class MyDSpaceBulkActionComponent implements OnInit, OnDestroy{
32+
33+
@Input()
34+
listId: string;
35+
36+
processing$: EventEmitter<boolean> = new EventEmitter<boolean>();
37+
38+
claimEnabled$: Observable<boolean>;
39+
claimedTaskActionsEnabled$: Observable<boolean>;
40+
41+
private successfullItems = 0;
42+
private errorItems = 0;
43+
private subs: Subscription[] = [];
44+
45+
private readonly approveOption = WORKFLOW_TASK_OPTION_APPROVE;
46+
47+
constructor(
48+
protected selectableListService: SelectableListService,
49+
protected poolTaskDataService: PoolTaskDataService,
50+
protected claimedTaskService: ClaimedTaskDataService,
51+
protected notificationsService: NotificationsService,
52+
protected translate: TranslateService,
53+
protected router: Router,
54+
protected searchService: SearchService,
55+
protected requestService: RequestService,
56+
) { }
57+
58+
ngOnInit() {
59+
this.claimEnabled$ = this.getActionEnabled('claimaction');
60+
this.claimedTaskActionsEnabled$ = this.getActionEnabled('claimedtask');
61+
this.getSelectedList().subscribe(console.log);
62+
}
63+
64+
ngOnDestroy() {
65+
if (hasValue(this.subs)) {
66+
this.subs.forEach(sub => sub.unsubscribe());
67+
}
68+
}
69+
70+
claimAllSelectedTask() {
71+
this.subs.push(
72+
this.getSelectedList().pipe(
73+
take(1),
74+
map(list => list.map(item =>
75+
this.poolTaskDataService.getPoolTaskEndpointById(item.indexableObject.id)
76+
)
77+
),
78+
switchMap(endPoints => combineLatest(...endPoints)),
79+
map(tasksHref => tasksHref.filter(data => hasValue(data))
80+
.map(href => this.claimItemByHref(href))
81+
),
82+
switchMap(claims => combineLatest(...claims))
83+
).subscribe(() => {
84+
this.handlePageReload();
85+
})
86+
);
87+
}
88+
89+
getSelectedList(): Observable<PoolTaskSearchResult[] | ClaimedTaskSearchResult[]> {
90+
return this.selectableListService.getSelectableList(this.listId).pipe(
91+
filter(data => !!data),
92+
distinctUntilChanged(),
93+
map(task => task.selection as (PoolTaskSearchResult[] | ClaimedTaskSearchResult[])),
94+
);
95+
}
96+
97+
getActionEnabled(actionType: string): Observable<boolean> {
98+
return this.getSelectedList().pipe(
99+
map(list => list.map(
100+
item => (item._embedded.indexableObject.action || item._embedded.indexableObject.type) as any as string
101+
).some(
102+
action => action === actionType
103+
)
104+
),
105+
);
106+
}
107+
108+
claimItemByHref(href: string): Observable<ProcessTaskResponse> {
109+
const id = href.substring(href.lastIndexOf('/') + 1, href.length);
110+
return this.getSelectedList().pipe(
111+
take(1),
112+
map(list => list.find(item => item.indexableObject.id.toString() === id)),
113+
switchMap(item => this.claimedTaskService.claimTask(item.indexableObject.id, href)),
114+
map((response) => {
115+
if (response.hasSucceeded) {
116+
this.successfullItems += 1;
117+
} else {
118+
this.errorItems += 1;
119+
}
120+
121+
return response;
122+
})
123+
);
124+
}
125+
126+
127+
approveItem(id: string, options): Observable<ProcessTaskResponse> {
128+
return this.claimedTaskService.submitTask(id, options).pipe(
129+
take(1),
130+
map((response) => {
131+
if (response.hasSucceeded) {
132+
this.successfullItems += 1;
133+
} else {
134+
this.errorItems += 1;
135+
}
136+
137+
return response;
138+
})
139+
);
140+
}
141+
142+
notifyOperationStatus(succeeded: boolean, itemNumber: number) {
143+
if (succeeded) {
144+
this.notificationsService.success(null,
145+
this.translate.get('submission.workflow.tasks.bulk.item.success', {itemNumber}),
146+
new NotificationOptions(5000, false)
147+
);
148+
} else {
149+
this.notificationsService.error(null,
150+
this.translate.get('submission.workflow.tasks.bulk.item.error', {itemNumber}),
151+
new NotificationOptions(20000, true));
152+
}
153+
}
154+
155+
reload(): void {
156+
this.router.navigated = false;
157+
const url = decodeURIComponent(this.router.url);
158+
// override the route reuse strategy
159+
this.router.routeReuseStrategy.shouldReuseRoute = () => {
160+
return false;
161+
};
162+
// This assures that the search cache is empty before reloading mydspace.
163+
// See https://github.com/DSpace/dspace-angular/pull/468
164+
this.searchService.getEndpoint().pipe(
165+
take(1),
166+
tap((cachedHref: string) => this.requestService.removeByHrefSubstring(cachedHref))
167+
).subscribe(() => this.router.navigateByUrl(url));
168+
}
169+
170+
handlePageReload(): void {
171+
if ( this.successfullItems > 0) {
172+
this.notifyOperationStatus(true, this.successfullItems);
173+
}
174+
if (this.errorItems > 0) {
175+
this.notifyOperationStatus(false, this.errorItems);
176+
}
177+
this.successfullItems = 0;
178+
this.errorItems = 0;
179+
this.selectableListService.deselectAll(this.listId);
180+
this.reload();
181+
}
182+
183+
submitAllSelectedTask() {
184+
this.subs.push(
185+
this.getSelectedList().pipe(
186+
take(1),
187+
map(list => list.map(item => this.approveItem(item.indexableObject.id, {
188+
[this.approveOption]: 'true'
189+
}))
190+
),
191+
switchMap(submissions => combineLatest(...submissions))
192+
).subscribe(() => {
193+
this.handlePageReload();
194+
})
195+
);
196+
}
197+
}

src/app/my-dspace-page/my-dspace-page.component.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import { ViewMode } from '../core/shared/view-mode.model';
1212
import { MyDSpaceRequest } from '../core/data/request.models';
1313
import { Context } from '../core/shared/context.model';
1414
import { RoleType } from '../core/roles/role-types';
15+
import { MyDSpaceConfigurationValueType } from './my-dspace-configuration-value-type';
16+
import { SelectableListService } from '../shared/object-list/selectable-list/selectable-list.service';
17+
import { PoolTaskSearchResult } from '../shared/object-collection/shared/pool-task-search-result.model';
1518

1619
export const MYDSPACE_ROUTE = '/mydspace';
1720
export const SEARCH_CONFIG_SERVICE: InjectionToken<SearchConfigurationService> = new InjectionToken<SearchConfigurationService>('searchConfigurationService');
@@ -37,7 +40,10 @@ export class MyDSpacePageComponent implements OnInit {
3740
* The list of available configuration options
3841
*/
3942
configurationList$: Observable<SearchConfigurationOption[]>;
40-
43+
/**
44+
* The current configuration option
45+
*/
46+
currentConfiguration$: Observable<string>;
4147
/**
4248
* The start context to use in the search: workspace or workflow
4349
*/
@@ -63,8 +69,18 @@ export class MyDSpacePageComponent implements OnInit {
6369
*/
6470
viewModeList = [ViewMode.ListElement, ViewMode.DetailedListElement];
6571

66-
constructor(private service: SearchService,
67-
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: MyDSpaceConfigurationService) {
72+
public readonly workflowType = MyDSpaceConfigurationValueType.Workflow;
73+
74+
/**
75+
* List Id for item selection
76+
*/
77+
listId = 'mydspace_selection_' + this.workflowType;
78+
79+
constructor(
80+
private service: SearchService,
81+
protected selectableListService: SelectableListService,
82+
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: MyDSpaceConfigurationService
83+
) {
6884
this.service.setServiceOptions(MyDSpaceResponseParsingService, MyDSpaceRequest);
6985
}
7086

@@ -88,6 +104,15 @@ export class MyDSpacePageComponent implements OnInit {
88104
this.context = configurationList[0].context;
89105
});
90106

107+
this.currentConfiguration$ = this.searchConfigService.getCurrentConfiguration('');
91108
}
92109

110+
onDeselectObject(task: PoolTaskSearchResult) {
111+
this.selectableListService.deselectSingle(this.listId, task);
112+
113+
}
114+
115+
onSelectObject(task: PoolTaskSearchResult) {
116+
this.selectableListService.selectSingle(this.listId, task);
117+
}
93118
}

src/app/my-dspace-page/my-dspace-page.module.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ import {
2323
import { ThemedMyDSpacePageComponent } from './themed-my-dspace-page.component';
2424
import { SearchModule } from '../shared/search/search.module';
2525
import { UploadModule } from '../shared/upload/upload.module';
26+
import {
27+
MyDSpaceBulkActionComponent
28+
} from './my-dspace-new-submission/my-dspace-bulk-action/my-dspace-bulk-action.component';
2629

2730
const DECLARATIONS = [
2831
MyDSpacePageComponent,
@@ -31,7 +34,8 @@ const DECLARATIONS = [
3134
CollectionSelectorComponent,
3235
MyDSpaceNewSubmissionDropdownComponent,
3336
MyDSpaceNewExternalDropdownComponent,
34-
MyDSpaceNewBulkImportComponent
37+
MyDSpaceNewBulkImportComponent,
38+
MyDSpaceBulkActionComponent
3539
];
3640

3741
@NgModule({

src/app/shared/search/search.component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
<div class="col-12" *ngIf="!(isXsOrSm$ | async)">
3131
<ng-template *ngTemplateOutlet="searchForm"></ng-template>
3232
<ng-content select="[additionalSearchOptions]"></ng-content>
33+
<ng-content select="[bulkActions]"></ng-content>
3334
</div>
3435
<div id="search-content" class="col-12">
3536
<div class="d-block d-md-none search-controls clearfix">

0 commit comments

Comments
 (0)