Skip to content

Commit 4a77e97

Browse files
committed
Merge branch 'dspace-cris-2024_02_x' into ux-plus-cris-2024_02_x
# Conflicts: # src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/row/metadata-container/metadata-container.component.spec.ts # src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/row/metadata-container/metadata-container.component.ts
2 parents f6fc9f5 + 7f410a9 commit 4a77e97

7 files changed

Lines changed: 126 additions & 8 deletions

File tree

src/app/app.component.spec.ts

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@ import { CommonModule } from '@angular/common';
22
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
33
import {
44
ComponentFixture,
5+
fakeAsync,
56
inject,
67
TestBed,
8+
tick,
79
waitForAsync,
810
} from '@angular/core/testing';
911
import {
1012
ActivatedRoute,
13+
NavigationStart,
1114
Router,
1215
} from '@angular/router';
1316
import {
@@ -19,7 +22,10 @@ import {
1922
TranslateLoader,
2023
TranslateModule,
2124
} from '@ngx-translate/core';
22-
import { of } from 'rxjs';
25+
import {
26+
of,
27+
Subject,
28+
} from 'rxjs';
2329

2430
import { APP_CONFIG } from '../config/app-config.interface';
2531
import { environment } from '../environments/environment';
@@ -46,7 +52,6 @@ import { MockActivatedRoute } from './shared/mocks/active-router.mock';
4652
import { AngularticsProviderMock } from './shared/mocks/angulartics-provider.service.mock';
4753
import { AuthServiceMock } from './shared/mocks/auth.service.mock';
4854
import { HeadTagServiceMock } from './shared/mocks/head-tag-service.mock';
49-
import { RouterMock } from './shared/mocks/router.mock';
5055
import { getMockThemeService } from './shared/mocks/theme-service.mock';
5156
import { TranslateLoaderMock } from './shared/mocks/translate-loader.mock';
5257
import { CSSVariableService } from './shared/sass-helper/css-variable.service';
@@ -65,6 +70,8 @@ const initialState = {
6570
core: { auth: { loading: false } },
6671
};
6772

73+
const itemPageUrl = '/entities/publication/3b6ef8e8-15a1-4607-abf8-2a6fbd572346';
74+
6875
export function getMockLocaleService(): LocaleService {
6976
return jasmine.createSpyObj('LocaleService', {
7077
setCurrentLanguageCode: jasmine.createSpy('setCurrentLanguageCode'),
@@ -77,13 +84,20 @@ describe('App component', () => {
7784
let routeServiceMock;
7885
let klaroServiceSpy: jasmine.SpyObj<KlaroService>;
7986
let datadogRumServiceSpy: jasmine.SpyObj<DatadogRumService>;
87+
let routerEventsObs: Subject<any>;
88+
let routerMock: Router;
8089

8190
const getDefaultTestBedConf = () => {
8291
breadcrumbsServiceSpy = jasmine.createSpyObj(['listenForRouteChanges']);
8392
routeServiceMock = jasmine.createSpyObj('RouterService', {
8493
getCurrentUrl: of('/home'),
8594
});
8695

96+
routerEventsObs = new Subject<any>();
97+
routerMock = jasmine.createSpyObj([], {
98+
events: routerEventsObs,
99+
});
100+
87101
klaroServiceSpy = jasmine.createSpyObj('KlaroService', {
88102
getSavedPreferences: jasmine.createSpy('getSavedPreferences'),
89103
watchConsentUpdates: jasmine.createSpy('watchConsentUpdates').and.returnValue(null),
@@ -112,7 +126,7 @@ describe('App component', () => {
112126
{ provide: HeadTagService, useValue: new HeadTagServiceMock() },
113127
{ provide: Angulartics2DSpace, useValue: new AngularticsProviderMock() },
114128
{ provide: AuthService, useValue: new AuthServiceMock() },
115-
{ provide: Router, useValue: new RouterMock() },
129+
{ provide: Router, useValue: routerMock },
116130
{ provide: ActivatedRoute, useValue: new MockActivatedRoute() },
117131
{ provide: MenuService, useValue: menuService },
118132
{ provide: CSSVariableService, useClass: CSSVariableServiceStub },
@@ -173,4 +187,32 @@ describe('App component', () => {
173187
});
174188

175189
});
190+
191+
describe('isRouteLoading$ handling', () => {
192+
193+
it('should not show loading for item page', fakeAsync(() => {
194+
routeServiceMock.getCurrentUrl.and.returnValue(of(itemPageUrl));
195+
routerEventsObs.next(new NavigationStart(1, itemPageUrl));
196+
fixture.detectChanges();
197+
tick();
198+
expect(comp.isRouteLoading$.value).toBeFalse();
199+
}));
200+
201+
it('should show loading for item page administrative edit', fakeAsync(() => {
202+
routeServiceMock.getCurrentUrl.and.returnValue(of(itemPageUrl));
203+
routerEventsObs.next(new NavigationStart(2, itemPageUrl + '/edit'));
204+
fixture.detectChanges();
205+
tick();
206+
expect(comp.isRouteLoading$.value).toBeTrue();
207+
}));
208+
209+
it('should not show loading navigating between item pages in administrative edit', fakeAsync(() => {
210+
routeServiceMock.getCurrentUrl.and.returnValue(of(itemPageUrl + '/edit'));
211+
routerEventsObs.next(new NavigationStart(2, itemPageUrl + '/edit/status'));
212+
fixture.detectChanges();
213+
tick();
214+
expect(comp.isRouteLoading$.value).toBeFalse();
215+
}));
216+
217+
});
176218
});

src/app/app.component.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,8 @@ export class AppComponent implements OnInit, AfterViewInit {
176176
)),
177177
).subscribe(([currentUrl, event]: [string, any]) => {
178178
if (event instanceof NavigationStart) {
179-
if (!(currentUrl.startsWith('/entities' || getEditItemPageRoute()) || currentUrl.startsWith(getWorkspaceItemModuleRoute()) || currentUrl.startsWith(getWorkflowItemModuleRoute()))) {
179+
const nextUrl = event.url;
180+
if (!this.shouldSkipLoadingStatus(currentUrl, nextUrl)) {
180181
distinctNext(this.isRouteLoading$, true);
181182
}
182183
// distinctNext(this.isRouteLoading$, true);
@@ -219,4 +220,14 @@ export class AppComponent implements OnInit, AfterViewInit {
219220
});
220221
}
221222

223+
private shouldSkipLoadingStatus(currentUrl: string, nextUrl: string): boolean {
224+
return ((currentUrl.startsWith('/entities') || currentUrl.startsWith(getEditItemPageRoute())) && !(this.isAdministrativeEditItemPageRoute(nextUrl, currentUrl)))
225+
|| currentUrl.startsWith(getWorkspaceItemModuleRoute()) || currentUrl.startsWith(getWorkflowItemModuleRoute());
226+
}
227+
228+
private isAdministrativeEditItemPageRoute(nextUrl: string, currentUrl: string): boolean {
229+
const editPageRegEx = /\/(entities\/[^\/]+|items)\/[0-9a-f-]{36}\/edit(?:\/.*)?$/;
230+
return editPageRegEx.test(nextUrl) && !editPageRegEx.test(currentUrl);
231+
}
232+
222233
}

src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/row/metadata-container/metadata-container.component.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,8 @@ describe('MetadataContainerComponent', () => {
511511
bitstreamField.bitstream.bundle,
512512
[ { metadataName: 'metadataFieldTest', metadataValue: 'metadataValueTest' } ],
513513
false,
514+
{},
515+
false,
514516
);
515517
});
516518
});
@@ -527,6 +529,8 @@ describe('MetadataContainerComponent', () => {
527529
bitstreamField.bitstream.bundle,
528530
[], // <--- empty array of filters,
529531
false, // <--- filterNonRestricted
532+
{},
533+
false,
530534
);
531535
});
532536
});

src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/row/metadata-container/metadata-container.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ export class MetadataContainerComponent implements OnInit {
239239
metadataValue: this.field.bitstream.metadataValue,
240240
});
241241
}
242-
return this.bitstreamDataService.findShowableBitstreamsByItem(this.item.uuid, this.field.bitstream.bundle, filters, false)
242+
return this.bitstreamDataService.findShowableBitstreamsByItem(this.item.uuid, this.field.bitstream.bundle, filters, false, {}, false)
243243
.pipe(
244244
getFirstCompletedRemoteData(),
245245
map((response: RemoteData<PaginatedList<Bitstream>>) => {

src/app/process-page/overview/process-overview.service.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,26 @@ export class ProcessOverviewService {
9898
);
9999
}
100100

101+
/**
102+
* Retrieve processes by their owner and status
103+
* @param processStatus The status for which to retrieve processes
104+
* @param findListOptions The FindListOptions object
105+
* @param autoRefreshingIntervalInMs Optional: The interval by which to automatically refresh the retrieved processes.
106+
* Leave empty or set to null to only retrieve the processes once.
107+
*/
108+
getOwnProcessesByProcessStatus(processStatus: ProcessStatus, findListOptions?: FindListOptions, autoRefreshingIntervalInMs: number = null) {
109+
const requestParam = new RequestParam('processStatus', processStatus);
110+
const options: FindListOptions = Object.assign(new FindListOptions(), {
111+
searchParams: [requestParam],
112+
elementsPerPage: 5,
113+
}, findListOptions);
114+
115+
if (hasValue(autoRefreshingIntervalInMs) && autoRefreshingIntervalInMs > 0) {
116+
this.processDataService.stopAutoRefreshing(processStatus);
117+
return this.processDataService.autoRefreshingSearchBy(processStatus, 'own', options, autoRefreshingIntervalInMs);
118+
} else {
119+
return this.processDataService.searchBy('own', options);
120+
}
121+
}
122+
101123
}

src/app/process-page/overview/table/process-overview-table.component.spec.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@ import {
1111
TranslateModule,
1212
TranslateService,
1313
} from '@ngx-translate/core';
14-
import { BehaviorSubject } from 'rxjs';
14+
import {
15+
BehaviorSubject,
16+
of,
17+
} from 'rxjs';
1518
import { take } from 'rxjs/operators';
1619

1720
import { AuthService } from '../../../core/auth/auth.service';
21+
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
1822
import { ProcessDataService } from '../../../core/data/processes/process-data.service';
1923
import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
2024
import { EPerson } from '../../../core/eperson/models/eperson.model';
@@ -52,6 +56,10 @@ describe('ProcessOverviewTableComponent', () => {
5256

5357
let translateServiceSpy: jasmine.SpyObj<TranslateService>;
5458

59+
const authorizationService = jasmine.createSpyObj('authorizationService', {
60+
isAuthorized: of(true),
61+
});
62+
5563
function init() {
5664
processes = [
5765
Object.assign(new Process(), {
@@ -104,6 +112,7 @@ describe('ProcessOverviewTableComponent', () => {
104112
sort: 'creationTime',
105113
},
106114
getProcessesByProcessStatus: createSuccessfulRemoteDataObject$(createPaginatedList(processes)).pipe(take(1)),
115+
getOwnProcessesByProcessStatus: createSuccessfulRemoteDataObject$(createPaginatedList(processes)).pipe(take(1)),
107116
});
108117
processService = jasmine.createSpyObj('processService', {
109118
searchBy: createSuccessfulRemoteDataObject$(createPaginatedList(processes)).pipe(take(1)),
@@ -153,6 +162,7 @@ describe('ProcessOverviewTableComponent', () => {
153162
{ provide: NgbModal, useValue: modalService },
154163
{ provide: AuthService, useValue: authService },
155164
{ provide: RouteService, useValue: routeService },
165+
{ provide: AuthorizationDataService, useValue: authorizationService },
156166
],
157167
schemas: [NO_ERRORS_SCHEMA],
158168
}).overrideComponent(ProcessOverviewTableComponent, {
@@ -227,6 +237,7 @@ describe('ProcessOverviewTableComponent', () => {
227237
});
228238

229239
describe('getEPersonName function', () => {
240+
230241
it('should return unknown user when id is null', (done: DoneFn) => {
231242
const id = null;
232243
const expectedTranslation = 'process.overview.unknown.user';
@@ -264,4 +275,16 @@ describe('ProcessOverviewTableComponent', () => {
264275
expect(translateServiceSpy.get).not.toHaveBeenCalled();
265276
});
266277
});
278+
279+
describe('when user is not admin', () => {
280+
beforeAll(waitForAsync(() => {
281+
authorizationService.isAuthorized.and.callFake(() => of(false));
282+
}));
283+
284+
it ('should call getOwnProcessesByProcessStatus', () => {
285+
expect(processOverviewService.getOwnProcessesByProcessStatus).toHaveBeenCalled();
286+
});
287+
288+
});
289+
267290
});

src/app/process-page/overview/table/process-overview-table.component.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ import { PaginationService } from 'src/app/core/pagination/pagination.service';
4040

4141
import { AuthService } from '../../../core/auth/auth.service';
4242
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
43+
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
44+
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
4345
import { FindListOptions } from '../../../core/data/find-list-options.model';
4446
import { PaginatedList } from '../../../core/data/paginated-list.model';
4547
import { RemoteData } from '../../../core/data/remote-data';
@@ -166,11 +168,13 @@ export class ProcessOverviewTableComponent implements OnInit, OnDestroy {
166168
protected router: Router,
167169
protected auth: AuthService,
168170
private translateService: TranslateService,
171+
protected authorizationService: AuthorizationDataService,
169172
@Inject(PLATFORM_ID) protected platformId: object,
170173
) {
171174
}
172175

173176
ngOnInit() {
177+
const isAdmin$ = this.isCurrentUserAdmin();
174178
// Only auto refresh on browsers
175179
if (!isPlatformBrowser(this.platformId)) {
176180
this.useAutoRefreshingSearchBy = false;
@@ -216,8 +220,16 @@ export class ProcessOverviewTableComponent implements OnInit, OnDestroy {
216220
this.processOverviewService.getFindListOptions(paginationOptions, this.sortField)),
217221
// Use the findListOptions to retrieve the relevant processes every interval
218222
switchMap((findListOptions: FindListOptions) =>
219-
this.processOverviewService.getProcessesByProcessStatus(
220-
this.processStatus, findListOptions, this.useAutoRefreshingSearchBy ? this.autoRefreshInterval : null),
223+
isAdmin$.pipe(
224+
switchMap((isAdmin: boolean) => {
225+
const autoRefreshInterval = this.useAutoRefreshingSearchBy ? this.autoRefreshInterval : null;
226+
if (isAdmin) {
227+
return this.processOverviewService.getProcessesByProcessStatus(this.processStatus, findListOptions, autoRefreshInterval);
228+
} else {
229+
return this.processOverviewService.getOwnProcessesByProcessStatus(this.processStatus, findListOptions, autoRefreshInterval);
230+
}
231+
}),
232+
),
221233
),
222234
// Redirect the user when he is logged out
223235
redirectOn4xx(this.router, this.auth),
@@ -310,4 +322,8 @@ export class ProcessOverviewTableComponent implements OnInit, OnDestroy {
310322
this.processOverviewService.stopAutoRefreshing(this.processStatus);
311323
}
312324

325+
isCurrentUserAdmin(): Observable<boolean> {
326+
return this.authorizationService.isAuthorized(FeatureID.AdministratorOf, undefined, undefined);
327+
}
328+
313329
}

0 commit comments

Comments
 (0)