Skip to content

Commit 7a8e667

Browse files
Merged ux-plus-cris into task/ux-plus-cris/UXP-87
2 parents d2de30c + dc202d2 commit 7a8e667

132 files changed

Lines changed: 2664 additions & 413 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
CYPRESS_BASE_URL: http://127.0.0.1:4000
3131
# When Chrome version is specified, we pin to a specific version of Chrome
3232
# Comment this out to use the latest release
33-
CHROME_VERSION: "116.0.5845.187-1"
33+
#CHROME_VERSION: "116.0.5845.187-1"
3434
# Bump Node heap size (OOM in CI after upgrading to Angular 15)
3535
NODE_OPTIONS: '--max-old-space-size=4096'
3636
# Project name to use when running "docker compose" prior to e2e tests
@@ -178,11 +178,12 @@ jobs:
178178
# Get homepage and verify that the <meta name="title"> tag includes "DSpace".
179179
# If it does, then SSR is working, as this tag is created by our MetadataService.
180180
# This step also prints entire HTML of homepage for easier debugging if grep fails.
181-
- name: Verify SSR (server-side rendering)
182-
run: |
183-
result=$(wget -O- -q http://127.0.0.1:4000/home)
184-
echo "$result"
185-
echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep DSpace
181+
# TODO: enable this step once we have a CRIS back end to test against
182+
# - name: Verify SSR (server-side rendering)
183+
# run: |
184+
# result=$(wget -O- -q http://127.0.0.1:4000/home)
185+
# echo "$result"
186+
# echo "$result" | grep -oE "<meta name=\"title\" [^>]*>" | grep DSpace
186187

187188
- name: Stop running app
188189
run: kill -9 $(lsof -t -i:4000)

config/config.example.yml

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,8 @@ submission:
201201
metadataDetailsList:
202202
- label: 'Document type'
203203
name: dc.type
204+
# Minimum number of characters required before performing a lookup.
205+
minChars: 3
204206

205207
# Default Language in which the UI will be rendered if the user's browser language is not an active language
206208
defaultLanguage: en
@@ -340,15 +342,6 @@ item:
340342
# The maximum number of values for repeatable metadata to show in the full item
341343
metadataLimit: 20
342344

343-
# When the search results are retrieved, for each item type the metadata with a valid authority value are inspected.
344-
# Referenced items will be fetched with a find all by id strategy to avoid individual rest requests
345-
# to efficiently display the search results.
346-
followAuthorityMetadata:
347-
- type: Publication
348-
metadata: dc.contributor.author
349-
- type: Product
350-
metadata: dc.contributor.author
351-
352345
# Community Page Config
353346
community:
354347
# Search tab config
@@ -422,10 +415,6 @@ themes:
422415
attributes:
423416
rel: manifest
424417
href: assets/dspace/images/favicons/manifest.webmanifest
425-
- tagName: link
426-
attributes:
427-
rel: stylesheet
428-
href: "https://fonts.googleapis.com/icon?family=Material+Icons"
429418

430419
# The default bundles that should always be displayed as suggestions when you upload a new bundle
431420
bundle:
@@ -629,3 +618,19 @@ addToAnyPlugin:
629618
title: DSpace CRIS 7 demo
630619
# The link to be shown in the shared post, if different from document.location.origin (optional)
631620
# link: https://dspacecris7.4science.cloud/
621+
622+
# When the search results are retrieved, for each item type the metadata with a valid authority value are inspected.
623+
# Referenced items will be fetched with a find all by id strategy to avoid individual rest requests
624+
# to efficiently display the search results.
625+
followAuthorityMetadata:
626+
- type: Publication
627+
metadata: dc.contributor.author
628+
- type: Product
629+
metadata: dc.contributor.author
630+
631+
# The maximum number of item to process when following authority metadata values.
632+
followAuthorityMaxItemLimit: 100
633+
634+
# The maximum number of metadata values to process for each metadata key
635+
# when following authority metadata values.
636+
followAuthorityMetadataValuesLimit: 5

cypress.config.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,19 @@ export default defineConfig({
4343
// It can be overridden via the CYPRESS_BASE_URL environment variable
4444
// (By default we set this to a value which should work in most development environments)
4545
baseUrl: 'http://localhost:4000',
46+
excludeSpecPattern: [
47+
'cypress/e2e/collection-create.cy.ts',
48+
'cypress/e2e/collection-edit.cy.ts',
49+
'cypress/e2e/collection-statistics.cy.ts',
50+
'cypress/e2e/community-edit.cy.ts',
51+
'cypress/e2e/community-statistics.cy.ts',
52+
'cypress/e2e/homepage.cy.ts',
53+
'cypress/e2e/item-edit.cy.ts',
54+
'cypress/e2e/item-template.cy.ts',
55+
'cypress/e2e/login-modal.cy.ts',
56+
'cypress/e2e/search-navbar.cy.ts',
57+
'cypress/e2e/search-page.cy.ts',
58+
]
4659
},
60+
defaultCommandTimeout: 10000,
4761
});

cypress/support/e2e.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ before(() => {
5656
beforeEach(() => {
5757
// Pre-agree to all Klaro cookies by setting the klaro-anonymous cookie
5858
// This just ensures it doesn't get in the way of matching other objects in the page.
59-
cy.setCookie('klaro-anonymous', '{%22authentication%22:true%2C%22preferences%22:true%2C%22acknowledgement%22:true%2C%22google-analytics%22:true%2C%22google-recaptcha%22:true}');
59+
cy.setCookie('klaro-anonymous', '{%22authentication%22:true%2C%22preferences%22:true%2C%22acknowledgement%22:true%2C%22google-analytics%22:true%2C%22google-recaptcha%22:true%2C%22plumX%22:true%2C%22altmetric%22:true%2C%22dimensions%22:true}');
6060

6161
// Remove any CSRF cookies saved from prior tests
6262
cy.clearCookie(DSPACE_XSRF_COOKIE);

src/app/app-routes.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
INFO_MODULE_PATH,
2020
INTERNAL_SERVER_ERROR,
2121
LEGACY_BITSTREAM_MODULE_PATH,
22+
PAGE_NOT_FOUND_PATH,
2223
PROFILE_MODULE_PATH,
2324
REGISTER_PATH,
2425
REQUEST_COPY_MODULE_PATH,
@@ -47,8 +48,8 @@ import { provideSubmissionState } from './submission/provide-submission-state';
4748
import { SUGGESTION_MODULE_PATH } from './suggestions-page/suggestions-page-routing-paths';
4849

4950
export const APP_ROUTES: Route[] = [
50-
{ path: INTERNAL_SERVER_ERROR, component: ThemedPageInternalServerErrorComponent },
51-
{ path: ERROR_PAGE, component: ThemedPageErrorComponent },
51+
{ path: INTERNAL_SERVER_ERROR, component: ThemedPageInternalServerErrorComponent, data: { title: INTERNAL_SERVER_ERROR } },
52+
{ path: ERROR_PAGE, component: ThemedPageErrorComponent, data: { title: ERROR_PAGE } },
5253
{
5354
path: '',
5455
canActivate: [authBlockingGuard],
@@ -61,6 +62,9 @@ export const APP_ROUTES: Route[] = [
6162
component: ThemedPageNotFoundComponent,
6263
pathMatch: 'full',
6364
canActivate: [reloadGuard],
65+
data: {
66+
title: PAGE_NOT_FOUND_PATH,
67+
},
6468
},
6569
{
6670
path: 'home',
@@ -237,6 +241,9 @@ export const APP_ROUTES: Route[] = [
237241
{
238242
path: FORBIDDEN_PATH,
239243
component: ThemedForbiddenComponent,
244+
data: {
245+
title: FORBIDDEN_PATH,
246+
},
240247
},
241248
{
242249
path: 'statistics',
@@ -314,7 +321,7 @@ export const APP_ROUTES: Route[] = [
314321
.then((m) => m.ROUTES),
315322
canActivate: [authenticatedGuard],
316323
},
317-
{ path: '**', pathMatch: 'full', component: ThemedPageNotFoundComponent },
324+
{ path: '**', pathMatch: 'full', component: ThemedPageNotFoundComponent, data: { title: PAGE_NOT_FOUND_PATH } },
318325
],
319326
},
320327
];

src/app/audit-page/object-audit-overview/object-audit-overview.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ <h4 class="mt-4 mb-4">{{ object.name }} (<em>{{object.type}}</em>)</h4>
6161

6262
</ng-container>
6363

64-
<h4 class="mt-4 mb-4" *ngIf="(auditsRD$ | async).statusCode === 404">{{'audit.object.overview.disabled.message' | translate}}</h4>
64+
<h4 class="mt-4 mb-4" *ngIf="(auditsRD$ | async)?.statusCode === 404">{{'audit.object.overview.disabled.message' | translate}}</h4>
6565

6666
</ng-container>
6767

src/app/audit-page/object-audit-overview/object-audit-overview.component.ts

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,21 @@ import { TranslateModule } from '@ngx-translate/core';
1818
import {
1919
combineLatest,
2020
Observable,
21+
of,
2122
} from 'rxjs';
22-
import { mergeMap } from 'rxjs/operators';
23+
import {
24+
map,
25+
mergeMap,
26+
switchMap,
27+
take,
28+
} from 'rxjs/operators';
2329

30+
import { COLLECTION_PAGE_LINKS_TO_FOLLOW } from '../../collection-page/collection-page.resolver';
2431
import { AuditDataService } from '../../core/audit/audit-data.service';
2532
import { Audit } from '../../core/audit/model/audit.model';
2633
import { AuthService } from '../../core/auth/auth.service';
2734
import { SortDirection } from '../../core/cache/models/sort-options.model';
35+
import { CollectionDataService } from '../../core/data/collection-data.service';
2836
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
2937
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
3038
import { FindListOptions } from '../../core/data/find-list-options.model';
@@ -33,6 +41,8 @@ import { PaginatedList } from '../../core/data/paginated-list.model';
3341
import { RemoteData } from '../../core/data/remote-data';
3442
import { PaginationService } from '../../core/pagination/pagination.service';
3543
import { redirectOn4xx } from '../../core/shared/authorized.operators';
44+
import { Collection } from '../../core/shared/collection.model';
45+
import { Item } from '../../core/shared/item.model';
3646
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
3747
import { PaginationComponent } from '../../shared/pagination/pagination.component';
3848
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
@@ -61,7 +71,7 @@ export class ObjectAuditOverviewComponent implements OnInit {
6171
/**
6272
* The object extracted from the route.
6373
*/
64-
object;
74+
object: Item;
6575

6676
/**
6777
* List of all audits
@@ -92,14 +102,17 @@ export class ObjectAuditOverviewComponent implements OnInit {
92102
*/
93103
dateFormat = 'yyyy-MM-dd HH:mm:ss';
94104

105+
owningCollection$: Observable<Collection>;
106+
95107
constructor(protected authService: AuthService,
96108
protected route: ActivatedRoute,
97109
protected router: Router,
98110
protected auditService: AuditDataService,
99111
protected itemService: ItemDataService,
100112
protected authorizationService: AuthorizationDataService,
101-
protected paginationService: PaginationService) {
102-
}
113+
protected paginationService: PaginationService,
114+
protected collectionDataService: CollectionDataService,
115+
) {}
103116

104117
ngOnInit(): void {
105118
this.route.paramMap.pipe(
@@ -108,6 +121,15 @@ export class ObjectAuditOverviewComponent implements OnInit {
108121
redirectOn4xx(this.router, this.authService),
109122
).subscribe((rd) => {
110123
this.object = rd.payload;
124+
this.owningCollection$ = this.collectionDataService.findOwningCollectionFor(
125+
this.object,
126+
true,
127+
false,
128+
...COLLECTION_PAGE_LINKS_TO_FOLLOW,
129+
).pipe(
130+
getFirstCompletedRemoteData(),
131+
map(data => data?.payload),
132+
);
111133
this.setAudits();
112134
});
113135
}
@@ -118,17 +140,35 @@ export class ObjectAuditOverviewComponent implements OnInit {
118140
setAudits() {
119141
const config$ = this.paginationService.getFindListOptions(this.pageConfig.id, this.config, this.pageConfig);
120142
const isAdmin$ = this.isCurrentUserAdmin();
121-
this.auditsRD$ = combineLatest([isAdmin$, config$]).pipe(
122-
mergeMap(([isAdmin, config]) => {
143+
const parentCommunity$ = this.owningCollection$.pipe(
144+
switchMap(collection => collection.parentCommunity),
145+
getFirstCompletedRemoteData(),
146+
map(data => data?.payload),
147+
);
148+
149+
150+
this.auditsRD$ = combineLatest([isAdmin$, config$, this.owningCollection$, parentCommunity$]).pipe(
151+
mergeMap(([isAdmin, config, owningCollection, parentCommunity]) => {
123152
if (isAdmin) {
124-
return this.auditService.findByObject(this.object.id, config);
153+
return this.auditService.findByObject(this.object.id, config, owningCollection.id, parentCommunity.id);
125154
}
155+
156+
return of(null);
126157
}),
127158
);
128159
}
129160

130161
isCurrentUserAdmin(): Observable<boolean> {
131-
return this.authorizationService.isAuthorized(FeatureID.AdministratorOf, undefined, undefined);
162+
return combineLatest([
163+
this.authorizationService.isAuthorized(FeatureID.IsCollectionAdmin),
164+
this.authorizationService.isAuthorized(FeatureID.IsCommunityAdmin),
165+
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
166+
]).pipe(
167+
map(([isCollectionAdmin, isCommunityAdmin, isSiteAdmin]) => {
168+
return isCollectionAdmin || isCommunityAdmin || isSiteAdmin;
169+
}),
170+
take(1),
171+
);
132172
}
133173

134174
/**

src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<div class="container">
22
<div class="d-flex justify-content-between">
3-
<h1 class="h2">{{'bitstream.download.page' | translate:{ bitstream: dsoNameService.getName((bitstream$ | async)) } }}</h1>
3+
<span class="h2">{{'bitstream.download.page' | translate:{ bitstream: (fileName$ | async) } }}</span>
44
<div class="pt-3">
55
<button *ngIf="hasHistory" (click)="back()" class="btn btn-outline-secondary">
66
<i class="fas fa-arrow-left"></i> {{'bitstream.download.page.back' | translate}}

src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.spec.ts

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
import { TranslateModule } from '@ngx-translate/core';
1313
import { of as observableOf } from 'rxjs';
1414

15-
import { getForbiddenRoute } from '../../app-routing-paths';
1615
import { AuthService } from '../../core/auth/auth.service';
1716
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
1817
import { SignpostingDataService } from '../../core/data/signposting-data.service';
@@ -133,72 +132,4 @@ describe('BitstreamDownloadPageComponent', () => {
133132
expect(component).toBeTruthy();
134133
});
135134
});
136-
137-
describe('bitstream retrieval', () => {
138-
describe('when the user is authorized and not logged in', () => {
139-
beforeEach(waitForAsync(() => {
140-
init();
141-
(authService.isAuthenticated as jasmine.Spy).and.returnValue(observableOf(false));
142-
143-
initTestbed();
144-
}));
145-
beforeEach(() => {
146-
fixture = TestBed.createComponent(BitstreamDownloadPageComponent);
147-
component = fixture.componentInstance;
148-
fixture.detectChanges();
149-
});
150-
it('should redirect to the content link', () => {
151-
expect(hardRedirectService.redirect).toHaveBeenCalledWith('bitstream-content-link');
152-
});
153-
it('should add the signposting links', () => {
154-
expect(serverResponseService.setHeader).toHaveBeenCalled();
155-
});
156-
});
157-
describe('when the user is authorized and logged in', () => {
158-
beforeEach(waitForAsync(() => {
159-
init();
160-
initTestbed();
161-
}));
162-
beforeEach(() => {
163-
fixture = TestBed.createComponent(BitstreamDownloadPageComponent);
164-
component = fixture.componentInstance;
165-
fixture.detectChanges();
166-
});
167-
it('should redirect to an updated content link', () => {
168-
expect(hardRedirectService.redirect).toHaveBeenCalledWith('content-url-with-headers');
169-
});
170-
});
171-
describe('when the user is not authorized and logged in', () => {
172-
beforeEach(waitForAsync(() => {
173-
init();
174-
(authorizationService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false));
175-
initTestbed();
176-
}));
177-
beforeEach(() => {
178-
fixture = TestBed.createComponent(BitstreamDownloadPageComponent);
179-
component = fixture.componentInstance;
180-
fixture.detectChanges();
181-
});
182-
it('should navigate to the forbidden route', () => {
183-
expect(router.navigateByUrl).toHaveBeenCalledWith(getForbiddenRoute(), { skipLocationChange: true });
184-
});
185-
});
186-
describe('when the user is not authorized and not logged in', () => {
187-
beforeEach(waitForAsync(() => {
188-
init();
189-
(authService.isAuthenticated as jasmine.Spy).and.returnValue(observableOf(false));
190-
(authorizationService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false));
191-
initTestbed();
192-
}));
193-
beforeEach(() => {
194-
fixture = TestBed.createComponent(BitstreamDownloadPageComponent);
195-
component = fixture.componentInstance;
196-
fixture.detectChanges();
197-
});
198-
it('should navigate to the login page', () => {
199-
expect(authService.setRedirectUrl).toHaveBeenCalled();
200-
expect(router.navigateByUrl).toHaveBeenCalledWith('login');
201-
});
202-
});
203-
});
204135
});

0 commit comments

Comments
 (0)