Skip to content

Commit 41bdd62

Browse files
authored
Merge pull request #1664 from 4Science/CST-5677
[CST-5677] Improve item authorization page
2 parents 8591727 + 4c19d3a commit 41bdd62

8 files changed

Lines changed: 211 additions & 51 deletions

File tree

src/app/item-page/edit-item-page/edit-item-page.module.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { NgModule } from '@angular/core';
22
import { CommonModule } from '@angular/common';
33

4-
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
4+
import { NgbTooltipModule, NgbModule } from '@ng-bootstrap/ng-bootstrap';
55

66
import { SharedModule } from '../../shared/shared.module';
77
import { EditItemPageRoutingModule } from './edit-item-page.routing.module';
@@ -48,7 +48,8 @@ import { ResourcePoliciesModule } from '../../shared/resource-policies/resource-
4848
EditItemPageRoutingModule,
4949
SearchPageModule,
5050
DragDropModule,
51-
ResourcePoliciesModule
51+
ResourcePoliciesModule,
52+
NgbModule
5253
],
5354
declarations: [
5455
EditItemPageComponent,

src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.html

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,33 @@
11
<div class="container">
22
<ds-alert [type]="'alert-info'" [content]="'item.edit.authorizations.heading'"></ds-alert>
3-
<ds-resource-policies [resourceType]="'item'" [resourceUUID]="(getItemUUID() | async)"></ds-resource-policies>
4-
<ng-container *ngFor="let bundle of (getItemBundles() | async); trackById">
5-
<ds-resource-policies [resourceType]="'bundle'"
6-
[resourceUUID]="bundle.id"></ds-resource-policies>
7-
<ng-container *ngFor="let bitstream of (bundleBitstreamsMap.get(bundle.id) | async)?.page; trackById">
8-
<ds-resource-policies [resourceType]="'bitstream'"
9-
[resourceUUID]="bitstream.id"></ds-resource-policies>
3+
<ds-resource-policies [resourceType]="'item'" [resourceName]="(getItemName() | async)"
4+
[resourceUUID]="(getItemUUID() | async)">
5+
</ds-resource-policies>
6+
<ng-container *ngFor="let bundle of (bundles$ | async); trackById">
7+
<ds-resource-policies [resourceType]="'bundle'" [resourceUUID]="bundle.id" [resourceName]="bundle.name">
8+
</ds-resource-policies>
9+
<ng-container *ngIf="(bundleBitstreamsMap.get(bundle.id)?.bitstreams | async)?.length > 0">
10+
<div class="card auth-bitstream-container">
11+
<div class="card-header">
12+
<button type="button" class="btn btn-outline-primary" (click)="collapseArea(bundle.id)"
13+
[attr.aria-expanded]="false" [attr.aria-controls]="bundle.id">
14+
{{ 'collection.edit.item.authorizations.show-bitstreams-button' | translate }} {{bundle.name}}
15+
</button>
16+
</div>
17+
<div class="card-body" [id]="bundle.id" [ngbCollapse]="bundleBitstreamsMap.get(bundle.id).isCollapsed">
18+
<ng-container
19+
*ngFor="let bitstream of (bundleBitstreamsMap.get(bundle.id).bitstreams | async); trackById">
20+
<ds-resource-policies [resourceType]="'bitstream'" [resourceUUID]="bitstream.id"
21+
[resourceName]="bitstream.name"></ds-resource-policies>
22+
</ng-container>
23+
<div class="row justify-content-center" *ngIf="!bundleBitstreamsMap.get(bundle.id).allBitstreamsLoaded">
24+
<button type="button" class="btn btn-link" (click)="onBitstreamsLoad(bundle)">{{ 'collection.edit.item.authorizations.load-more-button' | translate }}</button>
25+
</div>
26+
</div>
27+
</div>
1028
</ng-container>
1129
</ng-container>
30+
<div class="row justify-content-center" *ngIf="!allBundlesLoaded">
31+
<button type="button" class="btn btn-link" (click)="onBundleLoad()">{{ 'collection.edit.item.authorizations.load-bundle-button' | translate }}</button>
32+
</div>
1233
</div>
13-
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.auth-bitstream-container {
2+
margin-top: -1em;
3+
margin-bottom: 1.5em;
4+
}

src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.spec.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
import { Observable } from 'rxjs/internal/Observable';
12
import { waitForAsync, ComponentFixture, inject, TestBed } from '@angular/core/testing';
23
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
34
import { ActivatedRoute } from '@angular/router';
45
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
5-
import { of as observableOf } from 'rxjs';
6+
import { of as observableOf, of } from 'rxjs';
67
import { TranslateModule } from '@ngx-translate/core';
78
import { cold } from 'jasmine-marbles';
8-
import { ItemAuthorizationsComponent } from './item-authorizations.component';
9+
import { ItemAuthorizationsComponent, BitstreamMapValue } from './item-authorizations.component';
910
import { Bitstream } from '../../../core/shared/bitstream.model';
1011
import { Bundle } from '../../../core/shared/bundle.model';
1112
import { Item } from '../../../core/shared/item.model';
@@ -57,8 +58,6 @@ describe('ItemAuthorizationsComponent test suite', () => {
5758
bitstreams: createSuccessfulRemoteDataObject$(createPaginatedList([bitstream3, bitstream4]))
5859
});
5960
const bundles = [bundle1, bundle2];
60-
const bitstreamList1: PaginatedList<Bitstream> = buildPaginatedList(new PageInfo(), [bitstream1, bitstream2]);
61-
const bitstreamList2: PaginatedList<Bitstream> = buildPaginatedList(new PageInfo(), [bitstream3, bitstream4]);
6261

6362
const item = Object.assign(new Item(), {
6463
uuid: 'item',
@@ -142,13 +141,12 @@ describe('ItemAuthorizationsComponent test suite', () => {
142141
expect(compAsAny.bundleBitstreamsMap.has('bundle1')).toBeTruthy();
143142
expect(compAsAny.bundleBitstreamsMap.has('bundle2')).toBeTruthy();
144143
let bitstreamList = compAsAny.bundleBitstreamsMap.get('bundle1');
145-
expect(bitstreamList).toBeObservable(cold('(a|)', {
146-
a: bitstreamList1
144+
expect(bitstreamList.bitstreams).toBeObservable(cold('(a|)', {
145+
a : [bitstream1, bitstream2]
147146
}));
148-
149147
bitstreamList = compAsAny.bundleBitstreamsMap.get('bundle2');
150-
expect(bitstreamList).toBeObservable(cold('(a|)', {
151-
a: bitstreamList2
148+
expect(bitstreamList.bitstreams).toBeObservable(cold('(a|)', {
149+
a: [bitstream3, bitstream4]
152150
}));
153151
});
154152

src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts

Lines changed: 148 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { isEqual } from 'lodash';
2+
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
13
import { Component, OnDestroy, OnInit } from '@angular/core';
24
import { ActivatedRoute } from '@angular/router';
35

@@ -6,7 +8,8 @@ import { catchError, filter, first, map, mergeMap, take } from 'rxjs/operators';
68

79
import { buildPaginatedList, PaginatedList } from '../../../core/data/paginated-list.model';
810
import {
9-
getFirstSucceededRemoteDataPayload, getFirstSucceededRemoteDataWithNotEmptyPayload,
11+
getFirstSucceededRemoteDataPayload,
12+
getFirstSucceededRemoteDataWithNotEmptyPayload,
1013
} from '../../../core/shared/operators';
1114
import { Item } from '../../../core/shared/item.model';
1215
import { followLink } from '../../../shared/utils/follow-link-config.model';
@@ -25,7 +28,8 @@ interface BundleBitstreamsMapEntry {
2528

2629
@Component({
2730
selector: 'ds-item-authorizations',
28-
templateUrl: './item-authorizations.component.html'
31+
templateUrl: './item-authorizations.component.html',
32+
styleUrls:['./item-authorizations.component.scss']
2933
})
3034
/**
3135
* Component that handles the item Authorizations
@@ -36,13 +40,13 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
3640
* A map that contains all bitstream of the item's bundles
3741
* @type {Observable<Map<string, Observable<PaginatedList<Bitstream>>>>}
3842
*/
39-
public bundleBitstreamsMap: Map<string, Observable<PaginatedList<Bitstream>>> = new Map<string, Observable<PaginatedList<Bitstream>>>();
43+
public bundleBitstreamsMap: Map<string, BitstreamMapValue> = new Map<string, BitstreamMapValue>();
4044

4145
/**
42-
* The list of bundle for the item
46+
* The list of all bundles for the item
4347
* @type {Observable<PaginatedList<Bundle>>}
4448
*/
45-
private bundles$: BehaviorSubject<Bundle[]> = new BehaviorSubject<Bundle[]>([]);
49+
bundles$: BehaviorSubject<Bundle[]> = new BehaviorSubject<Bundle[]>([]);
4650

4751
/**
4852
* The target editing item
@@ -56,32 +60,102 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
5660
*/
5761
private subs: Subscription[] = [];
5862

63+
/**
64+
* The size of the bundles to be loaded on demand
65+
* @type {number}
66+
*/
67+
bundlesPerPage = 6;
68+
69+
/**
70+
* The number of current page
71+
* @type {number}
72+
*/
73+
bundlesPageSize = 1;
74+
75+
/**
76+
* The flag to show or not the 'Load more' button
77+
* based on the condition if all the bundles are loaded or not
78+
* @type {boolean}
79+
*/
80+
allBundlesLoaded = false;
81+
82+
/**
83+
* Initial size of loaded bitstreams
84+
* The size of incrementation used in bitstream pagination
85+
*/
86+
bitstreamSize = 4;
87+
88+
/**
89+
* The size of the loaded bitstremas at a certain moment
90+
* @private
91+
*/
92+
private bitstreamPageSize = 4;
93+
5994
/**
6095
* Initialize instance variables
6196
*
6297
* @param {LinkService} linkService
6398
* @param {ActivatedRoute} route
99+
* @param nameService
64100
*/
65101
constructor(
66102
private linkService: LinkService,
67-
private route: ActivatedRoute
103+
private route: ActivatedRoute,
104+
private nameService: DSONameService
68105
) {
69106
}
70107

71108
/**
72109
* Initialize the component, setting up the bundle and bitstream within the item
73110
*/
74111
ngOnInit(): void {
112+
this.getBundlesPerItem();
113+
}
114+
115+
/**
116+
* Return the item's UUID
117+
*/
118+
getItemUUID(): Observable<string> {
119+
return this.item$.pipe(
120+
map((item: Item) => item.id),
121+
first((UUID: string) => isNotEmpty(UUID))
122+
);
123+
}
124+
125+
/**
126+
* Return the item's name
127+
*/
128+
getItemName(): Observable<string> {
129+
return this.item$.pipe(
130+
map((item: Item) => this.nameService.getName(item))
131+
);
132+
}
133+
134+
/**
135+
* Return all item's bundles
136+
*
137+
* @return an observable that emits all item's bundles
138+
*/
139+
getItemBundles(): Observable<Bundle[]> {
140+
return this.bundles$.asObservable();
141+
}
142+
143+
/**
144+
* Get all bundles per item
145+
* and all the bitstreams per bundle
146+
* @param page number of current page
147+
*/
148+
getBundlesPerItem(page: number = 1) {
75149
this.item$ = this.route.data.pipe(
76150
map((data) => data.dso),
77151
getFirstSucceededRemoteDataWithNotEmptyPayload(),
78152
map((item: Item) => this.linkService.resolveLink(
79153
item,
80-
followLink('bundles', {}, followLink('bitstreams'))
154+
followLink('bundles', {findListOptions: {currentPage : page, elementsPerPage: this.bundlesPerPage}}, followLink('bitstreams'))
81155
))
82156
) as Observable<Item>;
83157

84-
const bundles$: Observable<PaginatedList<Bundle>> = this.item$.pipe(
158+
const bundles$: Observable<PaginatedList<Bundle>> = this.item$.pipe(
85159
filter((item: Item) => isNotEmpty(item.bundles)),
86160
mergeMap((item: Item) => item.bundles),
87161
getFirstSucceededRemoteDataWithNotEmptyPayload(),
@@ -96,37 +170,36 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
96170
take(1),
97171
map((list: PaginatedList<Bundle>) => list.page)
98172
).subscribe((bundles: Bundle[]) => {
99-
this.bundles$.next(bundles);
173+
if (isEqual(bundles.length,0) || bundles.length < this.bundlesPerPage) {
174+
this.allBundlesLoaded = true;
175+
}
176+
if (isEqual(page, 1)) {
177+
this.bundles$.next(bundles);
178+
} else {
179+
this.bundles$.next(this.bundles$.getValue().concat(bundles));
180+
}
100181
}),
101182
bundles$.pipe(
102183
take(1),
103184
mergeMap((list: PaginatedList<Bundle>) => list.page),
104185
map((bundle: Bundle) => ({ id: bundle.id, bitstreams: this.getBundleBitstreams(bundle) }))
105186
).subscribe((entry: BundleBitstreamsMapEntry) => {
106-
this.bundleBitstreamsMap.set(entry.id, entry.bitstreams);
187+
let bitstreamMapValues: BitstreamMapValue = {
188+
isCollapsed: true,
189+
allBitstreamsLoaded: false,
190+
bitstreams: null
191+
};
192+
bitstreamMapValues.bitstreams = entry.bitstreams.pipe(
193+
map((b: PaginatedList<Bitstream>) => {
194+
bitstreamMapValues.allBitstreamsLoaded = b?.page.length < this.bitstreamSize;
195+
return [...b.page.slice(0, this.bitstreamSize)];
196+
})
197+
);
198+
this.bundleBitstreamsMap.set(entry.id, bitstreamMapValues);
107199
})
108200
);
109201
}
110202

111-
/**
112-
* Return the item's UUID
113-
*/
114-
getItemUUID(): Observable<string> {
115-
return this.item$.pipe(
116-
map((item: Item) => item.id),
117-
first((UUID: string) => isNotEmpty(UUID))
118-
);
119-
}
120-
121-
/**
122-
* Return all item's bundles
123-
*
124-
* @return an observable that emits all item's bundles
125-
*/
126-
getItemBundles(): Observable<Bundle[]> {
127-
return this.bundles$.asObservable();
128-
}
129-
130203
/**
131204
* Return all bundle's bitstreams
132205
*
@@ -142,6 +215,46 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
142215
);
143216
}
144217

218+
/**
219+
* Changes the collapsible state of the area that contains the bitstream list
220+
* @param bundleId Id of bundle responsible for the requested bitstreams
221+
*/
222+
collapseArea(bundleId: string) {
223+
this.bundleBitstreamsMap.get(bundleId).isCollapsed = !this.bundleBitstreamsMap.get(bundleId).isCollapsed;
224+
}
225+
226+
/**
227+
* Loads as much bundles as initial value of bundleSize to be displayed
228+
*/
229+
onBundleLoad(){
230+
this.bundlesPageSize ++;
231+
this.getBundlesPerItem(this.bundlesPageSize);
232+
}
233+
234+
/**
235+
* Calculates the bitstreams that are going to be loaded on demand,
236+
* based on the number configured on this.bitstreamSize.
237+
* @param bundle parent of bitstreams that are requested to be shown
238+
* @returns Subscription
239+
*/
240+
onBitstreamsLoad(bundle: Bundle) {
241+
return this.getBundleBitstreams(bundle).subscribe((res: PaginatedList<Bitstream>) => {
242+
let nextBitstreams = res?.page.slice(this.bitstreamPageSize, this.bitstreamPageSize + this.bitstreamSize);
243+
let bitstreamsToShow = this.bundleBitstreamsMap.get(bundle.id).bitstreams.pipe(
244+
map((existingBits: Bitstream[])=> {
245+
return [... existingBits, ...nextBitstreams];
246+
})
247+
);
248+
this.bitstreamPageSize = this.bitstreamPageSize + this.bitstreamSize;
249+
let bitstreamMapValues: BitstreamMapValue = {
250+
bitstreams: bitstreamsToShow ,
251+
isCollapsed: this.bundleBitstreamsMap.get(bundle.id).isCollapsed,
252+
allBitstreamsLoaded: res?.page.length <= this.bitstreamPageSize
253+
};
254+
this.bundleBitstreamsMap.set(bundle.id, bitstreamMapValues);
255+
});
256+
}
257+
145258
/**
146259
* Unsubscribe from all subscriptions
147260
*/
@@ -151,3 +264,9 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
151264
.forEach((subscription) => subscription.unsubscribe());
152265
}
153266
}
267+
268+
export interface BitstreamMapValue {
269+
bitstreams: Observable<Bitstream[]>;
270+
isCollapsed: boolean;
271+
allBitstreamsLoaded: boolean;
272+
}

src/app/shared/resource-policies/resource-policies.component.html

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@
44
<tr>
55
<th colspan="10">
66
<div class="d-flex justify-content-between align-items-center m-0">
7-
{{ 'resource-policies.table.headers.title.for.' + resourceType | translate }} {{resourceUUID}}
7+
<span>
8+
{{ 'resource-policies.table.headers.title.for.' + resourceType | translate }}
9+
<span class="text-info"> {{resourceName}} </span>
10+
<ng-container *ngIf="resourceType != 'item'">
11+
({{resourceUUID}})
12+
</ng-container>
13+
</span>
814
<div class="space-children-mr">
9-
<button class="btn btn-danger"
15+
<button class="btn btn-danger p-1"
1016
[disabled]="(!(canDelete() | async)) || (isProcessingDelete() | async)"
1117
[title]="'resource-policies.delete.btn.title' | translate"
1218
(click)="deleteSelectedResourcePolicies()">
@@ -18,7 +24,7 @@
1824
{{'resource-policies.delete.btn' | translate}}
1925
</span>
2026
</button>
21-
<button class="btn btn-success"
27+
<button class="btn btn-success p-1"
2228
[disabled]="(isProcessingDelete() | async)"
2329
[title]="'resource-policies.add.for.' + resourceType | translate"
2430
(click)="redirectToResourcePolicyCreatePage()">

0 commit comments

Comments
 (0)