Skip to content

Commit 02b12f1

Browse files
Merged dspace-cris-2025_02_x into task/dspace-cris-2025_02_x/DSC-1956
2 parents fb3c2da + 20f8d55 commit 02b12f1

26 files changed

Lines changed: 743 additions & 126 deletions

bitbucket-pipelines.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ definitions:
278278
- 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)}')
279279
- git clone https://${BB_USER}:${BB_USER_TOKEN}@${DSPACE_VALUES_REPO}
280280
- cd dspace-values
281-
- '[ -f */test/"${BRANCH_FILE}" ] && sed -i "s/^\([[:space:]]*replicaCount:\) 0/\1 1/" "staging/${BRANCH_FILE}"'
281+
- '[ -f */test/"${BRANCH_FILE}" ] && sed -i "s/^\([[:space:]]*replicaCount:\) 0/\1 1/" */test/"${BRANCH_FILE}"'
282282
- git config --global user.email "${BB_USER}"
283283
- git config --global user.name "${BB_EMAIL}"
284284
- git commit -am "Enable test environment for ${BRANCH_NAME}" || echo "No changes to commit"
@@ -401,6 +401,10 @@ pipelines:
401401
- step: *turn-on-staging
402402
turn-on-test:
403403
- step: *turn-on-test
404+
build-images:
405+
- step: *check-branch-name-allowed
406+
- step: *angular-build
407+
- step: *build-and-push
404408
branches:
405409
'main-cris':
406410
- step: *check-branch-name-allowed

src/app/app.metareducers.spec.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { universalMetaReducer } from './app.metareducers';
2+
import { StoreActionTypes } from './store.actions';
3+
4+
describe('universalMetaReducer', () => {
5+
const mockReducer = (state: any, action: any) => state;
6+
7+
it('should pass state through for unknown action', () => {
8+
const state = { core: { route: { queryParams: { a: '1' }, params: {} } } };
9+
const result = universalMetaReducer(mockReducer)(state, { type: 'UNKNOWN' });
10+
expect(result).toEqual(state);
11+
});
12+
13+
describe('REHYDRATE', () => {
14+
it('should merge payload into state', () => {
15+
const state = { otherProp: 'keep-me' };
16+
const payload = { core: { route: { queryParams: { f: ['x'] }, params: {} } } };
17+
const result = universalMetaReducer(mockReducer)(state, {
18+
type: StoreActionTypes.REHYDRATE,
19+
payload,
20+
});
21+
expect(result.otherProp).toBe('keep-me');
22+
expect(result.core.route).toEqual({ queryParams: {}, params: {} });
23+
});
24+
25+
it('should reset core.route to empty after rehydration', () => {
26+
const state = { core: { route: { queryParams: { f: ['x'] }, params: { id: '123' } } } };
27+
const payload = { core: { route: { queryParams: { stale: ['val'] }, params: { stale: 'val' } } } };
28+
const result = universalMetaReducer(mockReducer)(state, {
29+
type: StoreActionTypes.REHYDRATE,
30+
payload,
31+
});
32+
expect(result.core.route).toEqual({ queryParams: {}, params: {} });
33+
});
34+
35+
it('should not crash when state.core is undefined', () => {
36+
const state = {};
37+
const payload = { core: { route: { queryParams: { a: '1' }, params: {} } } };
38+
const result = universalMetaReducer(mockReducer)(state, {
39+
type: StoreActionTypes.REHYDRATE,
40+
payload,
41+
});
42+
expect(result.core.route).toEqual({ queryParams: {}, params: {} });
43+
});
44+
45+
it('should preserve other core properties when resetting route', () => {
46+
const state = { core: { route: {} } };
47+
const payload = { core: { route: { queryParams: { x: '1' }, params: {} }, otherProp: 'keep-me' } };
48+
const result = universalMetaReducer(mockReducer)(state, {
49+
type: StoreActionTypes.REHYDRATE,
50+
payload,
51+
});
52+
expect(result.core.otherProp).toBe('keep-me');
53+
expect(result.core.route).toEqual({ queryParams: {}, params: {} });
54+
});
55+
56+
it('should handle REPLAY action (no-op)', () => {
57+
const state = { core: { route: { queryParams: { a: '1' }, params: {} } } };
58+
const result = universalMetaReducer(mockReducer)(state, {
59+
type: StoreActionTypes.REPLAY,
60+
});
61+
expect(result).toEqual(state);
62+
});
63+
});
64+
});

src/app/app.metareducers.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ export function universalMetaReducer(reducer) {
1919
switch (action.type) {
2020
case StoreActionTypes.REHYDRATE:
2121
state = Object.assign({}, state, action.payload);
22+
if (state.core) {
23+
state.core = Object.assign({}, state.core, {
24+
route: { queryParams: {}, params: {} },
25+
});
26+
}
2227
break;
2328
case StoreActionTypes.REPLAY:
2429
default:

src/app/core/auth/auth.actions.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const AuthActionTypes = {
3535
LOG_OUT_ERROR: type('dspace/auth/LOG_OUT_ERROR'),
3636
LOG_OUT_SUCCESS: type('dspace/auth/LOG_OUT_SUCCESS'),
3737
SET_REDIRECT_URL: type('dspace/auth/SET_REDIRECT_URL'),
38+
SET_REDIRECT_URL_AND_NAVIGATE: type('dspace/auth/SET_REDIRECT_URL_AND_NAVIGATE'),
3839
RETRIEVE_AUTHENTICATED_EPERSON: type('dspace/auth/RETRIEVE_AUTHENTICATED_EPERSON'),
3940
RETRIEVE_AUTHENTICATED_EPERSON_SUCCESS: type('dspace/auth/RETRIEVE_AUTHENTICATED_EPERSON_SUCCESS'),
4041
RETRIEVE_AUTHENTICATED_EPERSON_ERROR: type('dspace/auth/RETRIEVE_AUTHENTICATED_EPERSON_ERROR'),
@@ -360,6 +361,23 @@ export class SetRedirectUrlAction implements Action {
360361
}
361362
}
362363

364+
/**
365+
* Change the redirect url.
366+
* @class SetRedirectUrlAction
367+
* @implements {Action}
368+
*/
369+
export class SetRedirectUrlAndNavigateAction implements Action {
370+
public type: string = AuthActionTypes.SET_REDIRECT_URL_AND_NAVIGATE;
371+
payload: {
372+
redirectUrl: string;
373+
navigateUrl: string;
374+
};
375+
376+
constructor(redirectUrl: string, navigateUrl: string) {
377+
this.payload = { redirectUrl, navigateUrl };
378+
}
379+
}
380+
363381
/**
364382
* Start loading for a hard redirect
365383
* @class StartHardRedirectLoadingAction

src/app/core/auth/auth.effects.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ import {
7979
RetrieveAuthMethodsErrorAction,
8080
RetrieveAuthMethodsSuccessAction,
8181
RetrieveTokenAction,
82+
SetRedirectUrlAndNavigateAction,
8283
SetUserAsIdleAction,
8384
} from './auth.actions';
8485
// import services
@@ -164,6 +165,13 @@ export class AuthEffects {
164165
}),
165166
), { dispatch: false });
166167

168+
public redirectAndNavigate$: Observable<Action> = createEffect(() => this.actions$
169+
.pipe(ofType(AuthActionTypes.SET_REDIRECT_URL_AND_NAVIGATE),
170+
tap((action: SetRedirectUrlAndNavigateAction) => this.router.navigate([decodeURIComponent(action.payload.navigateUrl)])),
171+
),
172+
{ dispatch: false },
173+
);
174+
167175
// It means "reacts to this action but don't send another"
168176
public authenticatedError$: Observable<Action> = createEffect(() => this.actions$.pipe(
169177
ofType(AuthActionTypes.AUTHENTICATED_ERROR),

src/app/core/auth/auth.reducer.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
RetrieveAuthMethodsSuccessAction,
1818
SetAuthCookieStatus,
1919
SetRedirectUrlAction,
20+
SetRedirectUrlAndNavigateAction,
2021
} from './auth.actions';
2122
import { AuthMethod } from './models/auth.method';
2223
import { AuthMethodType } from './models/auth.method-type';
@@ -267,6 +268,11 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
267268
redirectUrl: (action as SetRedirectUrlAction).payload,
268269
});
269270

271+
case AuthActionTypes.SET_REDIRECT_URL_AND_NAVIGATE:
272+
return Object.assign({}, state, {
273+
redirectUrl: (action as SetRedirectUrlAndNavigateAction).payload.redirectUrl,
274+
});
275+
270276
case AuthActionTypes.REDIRECT_AFTER_LOGIN_SUCCESS:
271277
return Object.assign({}, state, {
272278
loading: true,

src/app/core/auth/auth.service.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ import {
6464
ResetAuthenticationMessagesAction,
6565
SetAuthCookieStatus,
6666
SetRedirectUrlAction,
67+
SetRedirectUrlAndNavigateAction,
6768
SetUserAsIdleAction,
6869
UnsetUserAsIdleAction,
6970
} from './auth.actions';
@@ -567,15 +568,19 @@ export class AuthService {
567568
/**
568569
* Set redirect url
569570
*/
570-
setRedirectUrl(url: string) {
571+
setRedirectUrl(redirectUrl: string, navigateUrl?: string) {
571572
// Add 1 hour to the current date
572573
const expireDate = Date.now() + (1000 * 60 * 60);
573574

574575
// Set the cookie expire date
575576
const expires = new Date(expireDate);
576577
const options: Cookies.CookieAttributes = { expires: expires };
577-
this.storage.set(REDIRECT_COOKIE, url, options);
578-
this.store.dispatch(new SetRedirectUrlAction(isNotUndefined(url) ? url : ''));
578+
this.storage.set(REDIRECT_COOKIE, redirectUrl, options);
579+
if (hasValue(navigateUrl)) {
580+
this.store.dispatch(new SetRedirectUrlAndNavigateAction(isNotUndefined(redirectUrl) ? redirectUrl : '', navigateUrl));
581+
} else {
582+
this.store.dispatch(new SetRedirectUrlAction(isNotUndefined(redirectUrl) ? redirectUrl : ''));
583+
}
579584
}
580585

581586
/**

src/app/core/data/processes/script-data.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export const BATCH_IMPORT_SCRIPT_NAME = 'import';
3434
export const BATCH_EXPORT_SCRIPT_NAME = 'export';
3535
export const ITEM_EXPORT_SCRIPT_NAME = 'item-export';
3636
export const BULK_ITEM_EXPORT_SCRIPT_NAME = 'bulk-item-export';
37-
export const DSPACE_OBJECT_DELETION_SCRIPT_NAME = 'dspace-object-deletion';
37+
export const DSPACE_OBJECT_DELETION_SCRIPT_NAME = 'object-deletion';
3838

3939
@Injectable({ providedIn: 'root' })
4040
export class ScriptDataService extends IdentifiableDataService<Script> implements FindAllData<Script> {
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { Params } from '@angular/router';
2+
3+
import {
4+
AddParameterAction,
5+
AddQueryParameterAction,
6+
ResetRouteStateAction,
7+
SetParameterAction,
8+
SetParametersAction,
9+
SetQueryParameterAction,
10+
SetQueryParametersAction,
11+
} from './route.actions';
12+
import {
13+
routeReducer,
14+
RouteState,
15+
} from './route.reducer';
16+
17+
describe('routeReducer', () => {
18+
const initialState: RouteState = {
19+
queryParams: {},
20+
params: {},
21+
};
22+
23+
it('should return initial state for unknown action', () => {
24+
const state = routeReducer(undefined, { type: 'UNKNOWN' } as any);
25+
expect(state).toEqual(initialState);
26+
});
27+
28+
describe('RESET', () => {
29+
it('should reset state to initialState', () => {
30+
const state: RouteState = {
31+
queryParams: { f: { author: ['Smith'] } },
32+
params: { id: '123', configuration: 'default' },
33+
};
34+
const result = routeReducer(state, new ResetRouteStateAction());
35+
expect(result).toEqual(initialState);
36+
});
37+
});
38+
39+
describe('SET_PARAMETERS', () => {
40+
it('should replace all params with action payload', () => {
41+
const state: RouteState = { ...initialState };
42+
const payload: Params = { id: '123', configuration: 'default' };
43+
const result = routeReducer(state, new SetParametersAction(payload));
44+
expect(result.params).toEqual(payload);
45+
expect(result.queryParams).toEqual(state.queryParams);
46+
});
47+
48+
it('should clear params when payload is empty', () => {
49+
const state: RouteState = {
50+
queryParams: {},
51+
params: { id: '123' },
52+
};
53+
const result = routeReducer(state, new SetParametersAction({}));
54+
expect(result.params).toEqual({});
55+
expect(result.queryParams).toEqual(state.queryParams);
56+
});
57+
});
58+
59+
describe('SET_QUERY_PARAMETERS', () => {
60+
it('should replace all queryParams with action payload', () => {
61+
const state: RouteState = { ...initialState };
62+
const payload: Params = { 'f.author': ['Smith'], 'spc.page': '1' };
63+
const result = routeReducer(state, new SetQueryParametersAction(payload));
64+
expect(result.queryParams).toEqual(payload);
65+
expect(result.params).toEqual(state.params);
66+
});
67+
68+
it('should clear queryParams when payload is empty', () => {
69+
const state: RouteState = {
70+
queryParams: { 'f.author': ['Smith'] },
71+
params: {},
72+
};
73+
const result = routeReducer(state, new SetQueryParametersAction({}));
74+
expect(result.queryParams).toEqual({});
75+
expect(result.params).toEqual(state.params);
76+
});
77+
});
78+
79+
describe('SET_PARAMETER', () => {
80+
it('should set a single param key-value pair', () => {
81+
const state: RouteState = {
82+
queryParams: {},
83+
params: { id: '123' },
84+
};
85+
const result = routeReducer(state, new SetParameterAction('configuration', 'default'));
86+
expect(result.params).toEqual({ id: '123', configuration: 'default' });
87+
});
88+
});
89+
90+
describe('SET_QUERY_PARAMETER', () => {
91+
it('should set a single query param key-value pair', () => {
92+
const state: RouteState = {
93+
queryParams: { 'f.author': ['Smith'] },
94+
params: {},
95+
};
96+
const result = routeReducer(state, new SetQueryParameterAction('tab', 'publications'));
97+
expect(result.queryParams).toEqual({ 'f.author': ['Smith'], tab: 'publications' });
98+
});
99+
});
100+
101+
describe('ADD_PARAMETER', () => {
102+
it('should append value to existing param key', () => {
103+
const state: RouteState = {
104+
queryParams: {},
105+
params: { tag: ['a'] },
106+
};
107+
const result = routeReducer(state, new AddParameterAction('tag', 'b'));
108+
expect(result.params.tag).toEqual(['a', 'b']);
109+
});
110+
111+
it('should create new param key with value array when key does not exist', () => {
112+
const state: RouteState = { ...initialState };
113+
const result = routeReducer(state, new AddParameterAction('tag', 'new'));
114+
expect(result.params.tag).toEqual(['new']);
115+
});
116+
});
117+
118+
describe('ADD_QUERY_PARAMETER', () => {
119+
it('should append value to existing query param key', () => {
120+
const state: RouteState = {
121+
queryParams: { 'f.author': ['Smith'] },
122+
params: {},
123+
};
124+
const result = routeReducer(state, new AddQueryParameterAction('f.author', 'Jones'));
125+
expect(result.queryParams['f.author']).toEqual(['Smith', 'Jones']);
126+
});
127+
128+
it('should create new query param key with value array when key does not exist', () => {
129+
const state: RouteState = { ...initialState };
130+
const result = routeReducer(state, new AddQueryParameterAction('tag', 'new'));
131+
expect(result.queryParams.tag).toEqual(['new']);
132+
});
133+
});
134+
});

0 commit comments

Comments
 (0)