Skip to content

Commit 4b139ac

Browse files
Merged dspace-cris-2025_02_x into task/dspace-cris-2025_02_x/DSC-2787
2 parents 13593ef + 4381651 commit 4b139ac

145 files changed

Lines changed: 2785 additions & 1884 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.

bitbucket-pipelines.yml

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ definitions:
1616
- echo "Using commit hash $HASH_COMMIT"
1717
- git config --global user.email "${BB_USER}"
1818
- git config --global user.name "${BB_EMAIL}"
19-
- git clone https://x-token-auth:${E2ERUNNERS_ACCESS_TOKEN}@${E2E_VALUES_REPO}
19+
- git clone https://${BB_USER}:${BB_USER_TOKEN}@${E2E_VALUES_REPO}
2020
- cd e2erunners-values
2121
- sed "s#HASH_COMMIT#${HASH_COMMIT}#g" TPL-cris-2025 > ${HASH_COMMIT}
2222
- sed -i "s#BRANCH_NAME#${BRANCH_NAME}#g" TPL-cris-2025 ${HASH_COMMIT}
2323
- git add ${HASH_COMMIT}
2424
- git commit -m "Add configuration for e2e-${HASH_COMMIT}" || echo "No changes to commit"
2525
- git push
2626
- cd ..
27-
- git clone https://x-token-auth:${HELM_CHARTS_ACCESS_TOKEN}@${HELM_CHARTS_REPO}
27+
- git clone https://${BB_USER}:${BB_USER_TOKEN}@${HELM_CHARTS_REPO}
2828
- cd helm-charts
2929
- PATH_VALUE=" e2e-$HASH_COMMIT"
3030
- printf " - name:%s\n" "$PATH_VALUE" >> ${E2E_VALUES}
@@ -42,15 +42,15 @@ definitions:
4242
- echo "Using commit hash $HASH_COMMIT"
4343
- git config --global user.email "${BB_USER}"
4444
- git config --global user.name "${BB_EMAIL}"
45-
- git clone https://x-token-auth:${E2ERUNNERS_ACCESS_TOKEN}@${E2E_VALUES_REPO}
45+
- git clone https://${BB_USER}:${BB_USER_TOKEN}@${E2E_VALUES_REPO}
4646
- cd e2erunners-values
4747
- sed "s#HASH_COMMIT#${HASH_COMMIT}#g" TPL-cris-2025 > ${HASH_COMMIT}
4848
- sed -i "s#BRANCH_NAME#${BRANCH_NAME}#g" TPL-cris-2025 ${HASH_COMMIT}
4949
- git add ${HASH_COMMIT}
5050
- git commit -m "Add configuration for e2e-${HASH_COMMIT}" || echo "No changes to commit"
5151
- git push
5252
- cd ..
53-
- git clone https://x-token-auth:${HELM_CHARTS_ACCESS_TOKEN}@${HELM_CHARTS_REPO}
53+
- git clone https://${BB_USER}:${BB_USER_TOKEN}@${HELM_CHARTS_REPO}
5454
- cd helm-charts
5555
- PATH_VALUE=" e2e-$HASH_COMMIT"
5656
- printf " - name:%s\n" "$PATH_VALUE" >> ${E2E_VALUES}
@@ -117,6 +117,7 @@ definitions:
117117
- export DSPACE_UI_PORT=4000
118118
- export DSPACE_CACHE_SERVERSIDE_BOTCACHE_MAX=0
119119
- export DSPACE_CACHE_SERVERSIDE_ANONYMOUSCACHE_MAX=0
120+
- export DSPACE_INFO_ENABLECOOKIECONSENTPOPUP=false
120121
- export CYPRESS_BASE_URL=http://127.0.0.1:4000
121122
- export CYPRESS_CACHE_FOLDER=~/.cache/Cypress
122123
- export CHROME_FLAGS="--no-sandbox --disable-dev-shm-usage --disable-gpu"
@@ -149,6 +150,23 @@ definitions:
149150
- echo "Running Cypress tests..."
150151
- npm run cypress:run --env chromeFlags="$CHROME_FLAGS"
151152
- echo "Test execution completed"
153+
- echo "Cleaning up e2e runner configuration for commit $HASH_COMMIT"
154+
- git config --global user.email "${BB_USER}"
155+
- git config --global user.name "${BB_EMAIL}"
156+
- git clone https://${BB_USER}:${BB_USER_TOKEN}@${E2E_VALUES_REPO}
157+
- cd e2erunners-values
158+
- rm -f ${HASH_COMMIT}
159+
- git add -A
160+
- git commit -m "Remove configuration for e2e-${HASH_COMMIT}" || echo "No changes to commit"
161+
- git push
162+
- cd ..
163+
- git clone https://${BB_USER}:${BB_USER_TOKEN}@${HELM_CHARTS_REPO}
164+
- cd helm-charts
165+
- 'sed -i "/^ - name: e2e-${HASH_COMMIT}$/d" ${E2E_VALUES}'
166+
- git add e2e-ingress/values.yaml
167+
- git commit -m "Remove ${HASH_COMMIT} from e2e-ingress values" || echo "No changes to commit"
168+
- git push
169+
- cd ..
152170
artifacts:
153171
- cypress/screenshots/**
154172
- cypress/videos/**
@@ -183,7 +201,7 @@ definitions:
183201
- export HASH_COMMIT=${BITBUCKET_COMMIT:0:8}
184202
- export BRANCH_NAME=$(echo "$BITBUCKET_BRANCH" | tr '[:upper:]' '[:lower:]' | sed 's|/|--|g')
185203
- export BRANCH_FILE=$(echo "$BITBUCKET_BRANCH" | awk -F'/' '{if(NF==1)val=$1;else if(NF==2)val=$2;else if(NF==3)val=$2;else val=$3;gsub(/_/, "-", val);print tolower(val)}')
186-
- git clone https://x-token-auth:${DSPACE_VALUES_ACCESS_TOKEN}@${DSPACE_VALUES_REPO}
204+
- git clone https://${BB_USER}:${BB_USER_TOKEN}@${DSPACE_VALUES_REPO}
187205
- cd dspace-values
188206
- '[ -f "dev/${BRANCH_FILE}" ] && sed -i "/^angular:/,/^[^ ]/s/\(tag: \).*/\1${BRANCH_NAME}-${HASH_COMMIT}/" "dev/${BRANCH_FILE}" && sed -i "s/^\([[:space:]]*replicaCount:\) 0/\1 1/" "dev/${BRANCH_FILE}"'
189207
- git config --global user.email "${BB_EMAIL}"
@@ -198,7 +216,7 @@ definitions:
198216
- export HASH_COMMIT=${BITBUCKET_COMMIT:0:8}
199217
- export BRANCH_NAME=$(echo "$BITBUCKET_BRANCH" | tr '[:upper:]' '[:lower:]' | sed 's|/|--|g')
200218
- export BRANCH_FILE=$(echo "$BITBUCKET_BRANCH" | awk -F'/' '{if(NF==1)val=$1;else if(NF==2)val=$2;else if(NF==3)val=$2;else val=$3;gsub(/_/, "-", val);print tolower(val)}')
201-
- git clone https://x-token-auth:${DSPACE_VALUES_ACCESS_TOKEN}@${DSPACE_VALUES_REPO}
219+
- git clone https://${BB_USER}:${BB_USER_TOKEN}@${DSPACE_VALUES_REPO}
202220
- cd dspace-values
203221
- '[ -f "staging/${BRANCH_FILE}" ] && sed -i "/^angular:/,/^[^ ]/s/\(tag: \).*/\1${BRANCH_NAME}-${HASH_COMMIT}/" "staging/${BRANCH_FILE}" && sed -i "s/^\([[:space:]]*replicaCount:\) 0/\1 1/" "staging/${BRANCH_FILE}"'
204222
- git config --global user.email "${BB_EMAIL}"
@@ -213,7 +231,7 @@ definitions:
213231
- export HASH_COMMIT=${BITBUCKET_COMMIT:0:8}
214232
- export BRANCH_NAME=$(echo "$BITBUCKET_BRANCH" | tr '[:upper:]' '[:lower:]' | sed 's|/|--|g')
215233
- export BRANCH_FILE=$(echo "$BITBUCKET_BRANCH" | awk -F'/' '{if(NF==1)val=$1;else if(NF==2)val=$2;else if(NF==3)val=$2;else val=$3;gsub(/_/, "-", val);print tolower(val)}')
216-
- git clone https://x-token-auth:${DSPACE_VALUES_ACCESS_TOKEN}@${DSPACE_VALUES_REPO}
234+
- git clone https://${BB_USER}:${BB_USER_TOKEN}@${DSPACE_VALUES_REPO}
217235
- cd dspace-values
218236
- '[ -f */test/"${BRANCH_FILE}" ] && sed -i "/^angular:/,/^[^ ]/s/\(tag: \).*/\1${BRANCH_NAME}-${HASH_COMMIT}/" */test/"${BRANCH_FILE}" && sed -i "s/^\([[:space:]]*replicaCount:\) 0/\1 1/" */test/"${BRANCH_FILE}"'
219237
- git config --global user.email "${BB_EMAIL}"
@@ -228,7 +246,7 @@ definitions:
228246
- export HASH_COMMIT=${BITBUCKET_COMMIT:0:8}
229247
- export BRANCH_NAME=$(echo "$BITBUCKET_BRANCH" | tr '[:upper:]' '[:lower:]' | sed 's|/|--|g')
230248
- export BRANCH_FILE=$(echo "$BITBUCKET_BRANCH" | awk -F'/' '{if(NF==1)val=$1;else if(NF==2)val=$2;else if(NF==3)val=$2;else val=$3;gsub(/_/, "-", val);print tolower(val)}')
231-
- git clone https://x-token-auth:${DSPACE_VALUES_ACCESS_TOKEN}@${DSPACE_VALUES_REPO}
249+
- git clone https://${BB_USER}:${BB_USER_TOKEN}@${DSPACE_VALUES_REPO}
232250
- cd dspace-values
233251
- '[ -f "dev/${BRANCH_FILE}" ] && sed -i "s/^\([[:space:]]*replicaCount:\) 0/\1 1/" "dev/${BRANCH_FILE}"'
234252
- git config --global user.email "${BB_USER}"
@@ -243,7 +261,7 @@ definitions:
243261
- export HASH_COMMIT=${BITBUCKET_COMMIT:0:8}
244262
- export BRANCH_NAME=$(echo "$BITBUCKET_BRANCH" | tr '[:upper:]' '[:lower:]' | sed 's|/|--|g')
245263
- export BRANCH_FILE=$(echo "$BITBUCKET_BRANCH" | awk -F'/' '{if(NF==1)val=$1;else if(NF==2)val=$2;else if(NF==3)val=$2;else val=$3;gsub(/_/, "-", val);print tolower(val)}')
246-
- git clone https://x-token-auth:${DSPACE_VALUES_ACCESS_TOKEN}@${DSPACE_VALUES_REPO}
264+
- git clone https://${BB_USER}:${BB_USER_TOKEN}@${DSPACE_VALUES_REPO}
247265
- cd dspace-values
248266
- '[ -f "staging/${BRANCH_FILE}" ] && sed -i "s/^\([[:space:]]*replicaCount:\) 0/\1 1/" "staging/${BRANCH_FILE}"'
249267
- git config --global user.email "${BB_USER}"
@@ -258,7 +276,7 @@ definitions:
258276
- export HASH_COMMIT=${BITBUCKET_COMMIT:0:8}
259277
- export BRANCH_NAME=$(echo "$BITBUCKET_BRANCH" | tr '[:upper:]' '[:lower:]' | sed 's|/|--|g')
260278
- export BRANCH_FILE=$(echo "$BITBUCKET_BRANCH" | awk -F'/' '{if(NF==1)val=$1;else if(NF==2)val=$2;else if(NF==3)val=$2;else val=$3;gsub(/_/, "-", val);print tolower(val)}')
261-
- git clone https://x-token-auth:${DSPACE_VALUES_ACCESS_TOKEN}@${DSPACE_VALUES_REPO}
279+
- git clone https://${BB_USER}:${BB_USER_TOKEN}@${DSPACE_VALUES_REPO}
262280
- cd dspace-values
263281
- '[ -f */test/"${BRANCH_FILE}" ] && sed -i "s/^\([[:space:]]*replicaCount:\) 0/\1 1/" "staging/${BRANCH_FILE}"'
264282
- git config --global user.email "${BB_USER}"

config/config.example.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,8 @@ item:
410410
pageSize: 5
411411
# Show the bitstream access status label on the item page
412412
showAccessStatuses: false
413+
# Open bitstream download links in a new browser tab by default
414+
openDownloadLinksInNewTab: true
413415
# The maximum number of metadata values to add to the metatag list of the item page
414416
metatagLimit: 20
415417
# The maximum number of values for repeatable metadata to show in the full item

server.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -719,10 +719,12 @@ function healthCheck(req, res) {
719719
const baseUrl = `${REST_BASE_URL}${environment.actuators.endpointPath}`;
720720
fetch(baseUrl)
721721
.then((response) => {
722-
res.status(response.status).send(response);
722+
return response.json().then((data) => {
723+
res.status(response.status).send(data);
724+
});
723725
})
724726
.catch((error) => {
725-
res.status(error.response.status).send({
727+
res.status(error?.response?.status || 503).send({
726728
error: error.message,
727729
});
728730
});

src/app/app.menus.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { HealthMenuProvider } from './shared/menu/providers/health.menu';
2525
import { ImportMenuProvider } from './shared/menu/providers/import.menu';
2626
import { ClaimMenuProvider } from './shared/menu/providers/item-claim.menu';
2727
import { OrcidMenuProvider } from './shared/menu/providers/item-orcid.menu';
28+
import { ItemStatisticsMenuProvider } from './shared/menu/providers/item-statistics.menu';
2829
import { VersioningMenuProvider } from './shared/menu/providers/item-versioning.menu';
2930
import { MetadataCmsMenuProvider } from './shared/menu/providers/metadata-cms.menu';
3031
import { NewMenuProvider } from './shared/menu/providers/new.menu';
@@ -58,6 +59,7 @@ export const MENUS = buildMenuStructure({
5859
CommunityListMenuProvider,
5960
ExploreMenuProvider,
6061
StatisticsMenuProvider,
62+
ItemStatisticsMenuProvider,
6163
],
6264
[MenuID.ADMIN]: [
6365
AdminCommunityListMenuProvider,
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/* eslint-disable import/no-namespace */
2+
import * as fileSaver from 'file-saver';
3+
import { ExportAsService } from 'ngx-export-as';
4+
import {
5+
BehaviorSubject,
6+
of,
7+
} from 'rxjs';
8+
9+
import { BrowserExportService } from './browser-export.service';
10+
import { ExportImageType } from './export.service';
11+
12+
describe('BrowserExportService', () => {
13+
let service: BrowserExportService;
14+
let mockDocument: any;
15+
let mockWindow: any;
16+
17+
beforeEach(() => {
18+
mockWindow = {
19+
nativeWindow: {
20+
location: {
21+
origin: 'http://localhost:9876',
22+
},
23+
},
24+
};
25+
26+
mockDocument = {
27+
styleSheets: [],
28+
};
29+
30+
service = new BrowserExportService('browser', mockWindow, mockDocument);
31+
});
32+
33+
describe('exportAsFile', () => {
34+
it('should create ExportAsService and call save', () => {
35+
const mockResult = of(null);
36+
spyOn(ExportAsService.prototype, 'save').and.returnValue(mockResult as any);
37+
38+
const result = service.exportAsFile('csv', 'element-id', 'test-file');
39+
40+
expect(ExportAsService.prototype.save).toHaveBeenCalled();
41+
expect(service.exportAsConfig).toEqual(jasmine.objectContaining({
42+
type: 'csv',
43+
elementIdOrContent: 'element-id',
44+
fileName: 'test-file',
45+
download: true,
46+
}));
47+
expect(result).toBe(mockResult);
48+
});
49+
50+
it('should pass download=false when specified', () => {
51+
spyOn(ExportAsService.prototype, 'save').and.returnValue(of(null) as any);
52+
53+
service.exportAsFile('xlsx', 'element-id', 'test-file', false);
54+
55+
expect(service.exportAsConfig.download).toBe(false);
56+
});
57+
});
58+
59+
describe('exportAsImage', () => {
60+
let domNode: HTMLElement;
61+
let isLoading: BehaviorSubject<boolean>;
62+
63+
beforeEach(() => {
64+
domNode = document.createElement('div');
65+
isLoading = new BehaviorSubject<boolean>(true);
66+
});
67+
68+
it('should call collectSameOriginFontCSS and set isLoading to false after export for png', async () => {
69+
spyOn(service, 'collectSameOriginFontCSS').and.returnValue('');
70+
71+
service.exportAsImage(domNode, ExportImageType.png, 'test-image', isLoading);
72+
await new Promise(resolve => setTimeout(resolve, 100));
73+
74+
expect(service.collectSameOriginFontCSS).toHaveBeenCalled();
75+
expect(isLoading.getValue()).toBe(false);
76+
});
77+
78+
it('should call collectSameOriginFontCSS and set isLoading to false after export for jpeg', async () => {
79+
spyOn(service, 'collectSameOriginFontCSS').and.returnValue('');
80+
81+
service.exportAsImage(domNode, ExportImageType.jpeg, 'test-image', isLoading);
82+
await new Promise(resolve => setTimeout(resolve, 100));
83+
84+
expect(service.collectSameOriginFontCSS).toHaveBeenCalled();
85+
expect(isLoading.getValue()).toBe(false);
86+
});
87+
88+
it('should pass fontEmbedCSS from collectSameOriginFontCSS to the export', async () => {
89+
const fakeFontCSS = '@font-face { font-family: Test; }';
90+
spyOn(service, 'collectSameOriginFontCSS').and.returnValue(fakeFontCSS);
91+
92+
service.exportAsImage(domNode, ExportImageType.png, 'test', isLoading);
93+
await new Promise(resolve => setTimeout(resolve, 100));
94+
95+
expect(service.collectSameOriginFontCSS).toHaveBeenCalled();
96+
});
97+
});
98+
99+
describe('exportImageWithBase64', () => {
100+
let isLoading: BehaviorSubject<boolean>;
101+
102+
beforeEach(() => {
103+
isLoading = new BehaviorSubject<boolean>(true);
104+
});
105+
106+
it('should call saveAs with the base64 string when it has a value', () => {
107+
spyOn(fileSaver, 'saveAs');
108+
const base64 = 'data:image/png;base64,abc123';
109+
110+
service.exportImageWithBase64(base64, ExportImageType.png, 'my-file', isLoading);
111+
112+
expect(fileSaver.saveAs).toHaveBeenCalledWith(base64, 'my-file.png');
113+
expect(isLoading.getValue()).toBe(false);
114+
});
115+
116+
it('should not call saveAs and should log error when base64 is null', () => {
117+
spyOn(fileSaver, 'saveAs');
118+
spyOn(console, 'error');
119+
120+
service.exportImageWithBase64(null, ExportImageType.jpeg, 'my-file', isLoading);
121+
122+
expect(fileSaver.saveAs).not.toHaveBeenCalled();
123+
expect(console.error).toHaveBeenCalledWith('Base64 string is empty');
124+
expect(isLoading.getValue()).toBe(false);
125+
});
126+
127+
it('should set isLoading to false regardless', () => {
128+
spyOn(fileSaver, 'saveAs');
129+
130+
service.exportImageWithBase64('data:image/jpeg;base64,xyz', ExportImageType.jpeg, 'file', isLoading);
131+
132+
expect(isLoading.getValue()).toBe(false);
133+
});
134+
});
135+
136+
describe('collectSameOriginFontCSS', () => {
137+
it('should skip cross-origin stylesheets', () => {
138+
const crossOriginSheet = { href: 'https://www.gstatic.com/charts/50/css/core/tooltip.css', cssRules: [] };
139+
mockDocument.styleSheets = [crossOriginSheet];
140+
141+
const fontCSS = service.collectSameOriginFontCSS();
142+
143+
expect(fontCSS).toBe('');
144+
});
145+
146+
it('should silently skip stylesheets that throw on cssRules access', () => {
147+
spyOn(console, 'error');
148+
149+
const badSheet = {
150+
href: null,
151+
get cssRules(): CSSRuleList {
152+
throw new DOMException('Not allowed');
153+
},
154+
};
155+
mockDocument.styleSheets = [badSheet];
156+
157+
const fontCSS = service.collectSameOriginFontCSS();
158+
159+
expect(console.error).toHaveBeenCalledWith('Error appending sheet to export: ', null);
160+
expect(fontCSS).toBe('');
161+
});
162+
163+
it('should collect @font-face rules from null-href (inline) stylesheets', () => {
164+
const fontRule1 = { constructor: { name: 'CSSFontFaceRule' }, cssText: '@font-face { font-family: A; }' };
165+
const fontRule2 = { constructor: { name: 'CSSFontFaceRule' }, cssText: '@font-face { font-family: B; }' };
166+
const nonFontRule = { constructor: { name: 'CSSStyleRule' }, cssText: 'body { margin: 0; }' };
167+
mockDocument.styleSheets = [{ href: null, cssRules: [fontRule1, nonFontRule, fontRule2] }];
168+
169+
const fontCSS = service.collectSameOriginFontCSS();
170+
171+
expect(fontCSS).toBe('@font-face { font-family: A; }\n@font-face { font-family: B; }');
172+
});
173+
});
174+
});
175+

0 commit comments

Comments
 (0)