Skip to content

Commit 4b959a2

Browse files
Andrea Barbassoatarix83
authored andcommitted
Merged in DSC-1570-from-maintenance (pull request DSpace#1534)
DSC-1570 from maintenance Approved-by: Giuseppe Digilio
2 parents fbc817f + d9fadbf commit 4b959a2

15 files changed

Lines changed: 299 additions & 1 deletion

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
"@angular/platform-server": "^15.2.8",
8585
"@angular/router": "^15.2.8",
8686
"@babel/runtime": "7.21.0",
87+
"@datadog/browser-rum": "^5.7.0",
8788
"@kolkov/ngx-gallery": "^2.0.1",
8889
"@material-ui/core": "^4.11.0",
8990
"@material-ui/icons": "^4.11.3",

src/app/app.component.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
3737
import { of } from 'rxjs';
3838
import { APP_CONFIG } from '../config/app-config.interface';
3939
import { environment } from '../environments/environment';
40+
import { KlaroService } from './shared/cookies/klaro.service';
4041

4142
let comp: AppComponent;
4243
let fixture: ComponentFixture<AppComponent>;
@@ -55,13 +56,21 @@ describe('App component', () => {
5556

5657
let breadcrumbsServiceSpy;
5758
let routeServiceMock;
59+
let klaroServiceSpy: jasmine.SpyObj<KlaroService>;
5860

5961
const getDefaultTestBedConf = () => {
6062
breadcrumbsServiceSpy = jasmine.createSpyObj(['listenForRouteChanges']);
6163
routeServiceMock = jasmine.createSpyObj('RouterService', {
6264
getCurrentUrl: of('/home')
6365
});
6466

67+
klaroServiceSpy = jasmine.createSpyObj('KlaroService', {
68+
getSavedPreferences: jasmine.createSpy('getSavedPreferences'),
69+
watchConsentUpdates: jasmine.createSpy('watchConsentUpdates')
70+
},{
71+
consentsUpdates$: of({})
72+
});
73+
6574
return {
6675
imports: [
6776
CommonModule,
@@ -89,6 +98,7 @@ describe('App component', () => {
8998
{ provide: BreadcrumbsService, useValue: breadcrumbsServiceSpy },
9099
{ provide: RouteService, useValue: routeServiceMock },
91100
{ provide: APP_CONFIG, useValue: environment },
101+
{ provide: KlaroService, useValue: klaroServiceSpy },
92102
provideMockStore({ initialState }),
93103
AppComponent,
94104
// RouteService

src/app/app.component.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { distinctNext } from './core/shared/distinct-next';
2929
import { RouteService } from './core/services/route.service';
3030
import { getEditItemPageRoute, getWorkflowItemModuleRoute, getWorkspaceItemModuleRoute } from './app-routing-paths';
3131
import { SocialService } from './social/social.service';
32+
import { DatadogRumService } from './shared/datadog-rum/datadog-rum.service';
3233

3334
@Component({
3435
selector: 'ds-app',
@@ -74,6 +75,7 @@ export class AppComponent implements OnInit, AfterViewInit {
7475
private modalService: NgbModal,
7576
private modalConfig: NgbModalConfig,
7677
private socialService: SocialService,
78+
private datadogRumService: DatadogRumService
7779
) {
7880
this.notificationOptions = environment.notifications;
7981

@@ -108,6 +110,10 @@ export class AppComponent implements OnInit, AfterViewInit {
108110
);
109111

110112
this.dispatchWindowSize(this._window.nativeWindow.innerWidth, this._window.nativeWindow.innerHeight);
113+
114+
if (isPlatformBrowser(this.platformId)) {
115+
this.datadogRumService.initDatadogRum();
116+
}
111117
}
112118

113119
private storeCSSVariables() {

src/app/core/core-state.model.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { JsonPatchOperationsState } from './json-patch/json-patch-operations.red
1111
import { MetaTagState } from './metadata/meta-tag.reducer';
1212
import { RouteState } from './services/route.reducer';
1313
import { RequestState } from './data/request-state.model';
14+
import { DatadogRumState } from '../shared/datadog-rum/datadog-rum.reducer';
1415

1516
/**
1617
* The core sub-state in the NgRx store
@@ -27,4 +28,5 @@ export interface CoreState {
2728
'json/patch': JsonPatchOperationsState;
2829
'metaTag': MetaTagState;
2930
'route': RouteState;
31+
'datadogRum': DatadogRumState;
3032
}

src/app/core/core.reducers.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
import { historyReducer } from './history/history.reducer';
1515
import { metaTagReducer } from './metadata/meta-tag.reducer';
1616
import { CoreState } from './core-state.model';
17+
import { datadogRumReducer } from '../shared/datadog-rum/datadog-rum.reducer';
1718

1819
export const coreReducers: ActionReducerMap<CoreState> = {
1920
'bitstreamFormats': bitstreamFormatReducer,
@@ -26,5 +27,6 @@ export const coreReducers: ActionReducerMap<CoreState> = {
2627
'auth': authReducer,
2728
'json/patch': jsonPatchOperationsReducer,
2829
'metaTag': metaTagReducer,
29-
'route': routeReducer
30+
'route': routeReducer,
31+
'datadogRum': datadogRumReducer,
3032
};

src/app/shared/cookies/browser-klaro.service.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,17 @@ export class BrowserKlaroService extends KlaroService {
117117
});
118118
}
119119

120+
if (environment.datadogRum?.clientToken && environment.datadogRum?.applicationId &&
121+
environment.datadogRum?.service && environment.datadogRum?.env) {
122+
this.klaroConfig.services.push(
123+
{
124+
name: 'datadog',
125+
purposes: ['thirdPartyJs'],
126+
required: false,
127+
}
128+
);
129+
}
130+
120131
const hideGoogleAnalytics$ = this.configService.findByPropertyName(this.GOOGLE_ANALYTICS_KEY).pipe(
121132
getFirstCompletedRemoteData(),
122133
map(remoteData => !remoteData.hasSucceeded || !remoteData.payload || isEmpty(remoteData.payload.values)),
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Action } from '@ngrx/store';
2+
import { type } from '../ngrx/type';
3+
4+
export const DatadogRumActionTypes = {
5+
SET_STATUS: type('dspace/datadog-rum/SET_IS_INITIALIZED'),
6+
};
7+
8+
export class setDatadogRumStatusAction implements Action {
9+
type = DatadogRumActionTypes.SET_STATUS;
10+
payload: {
11+
isInitialized?: boolean;
12+
isRunning?: boolean;
13+
};
14+
15+
constructor(status: { isInitialized?: boolean, isRunning?: boolean }) {
16+
this.payload = status;
17+
}
18+
}
19+
20+
export type DatadogRumAction = setDatadogRumStatusAction;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { DatadogRumAction, DatadogRumActionTypes } from './datadog-rum.actions';
2+
3+
export interface DatadogRumState {
4+
isInitialized: boolean;
5+
isRunning: boolean;
6+
}
7+
8+
const initialState: DatadogRumState = Object.create({isInitialized: false, isRunning: false});
9+
10+
export function datadogRumReducer(state = initialState, action: DatadogRumAction): DatadogRumState {
11+
12+
switch (action.type) {
13+
14+
case DatadogRumActionTypes.SET_STATUS: {
15+
return setDatadogRumStatus(state, action);
16+
}
17+
18+
default: {
19+
return state;
20+
}
21+
}
22+
}
23+
24+
function setDatadogRumStatus(state: DatadogRumState, action: DatadogRumAction) {
25+
return {
26+
...state,
27+
...action.payload
28+
};
29+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { TestBed } from '@angular/core/testing';
2+
import { MockStore, provideMockStore } from '@ngrx/store/testing';
3+
import { DatadogRumService } from './datadog-rum.service';
4+
import { CookieConsents, KlaroService } from '../cookies/klaro.service';
5+
import { of } from 'rxjs';
6+
import { environment } from '../../../environments/environment';
7+
import { setDatadogRumStatusAction } from './datadog-rum.actions';
8+
9+
describe('DatadogRumService', () => {
10+
let service: DatadogRumService;
11+
let store: MockStore;
12+
let klaroService: KlaroService;
13+
let memoizedSelector;
14+
15+
const initialState = {
16+
datadogRum: {
17+
isInitialized: false,
18+
isRunning: false
19+
}
20+
};
21+
22+
const consentsAccepted: CookieConsents = {
23+
datadog: true
24+
};
25+
26+
const klaroServiceSpy = jasmine.createSpyObj('KlaroService', {
27+
getSavedPreferences: jasmine.createSpy('getSavedPreferences'),
28+
watchConsentUpdates: jasmine.createSpy('watchConsentUpdates')
29+
}, {
30+
consentsUpdates$: of(consentsAccepted)
31+
});
32+
33+
const datadogRumEnvironmentOptions = {
34+
clientToken: 'clientToken',
35+
applicationId: 'applicationId',
36+
service: 'service',
37+
env: 'env'
38+
};
39+
40+
beforeEach(() => {
41+
TestBed.configureTestingModule({
42+
providers: [
43+
DatadogRumService,
44+
provideMockStore({initialState}),
45+
{provide: KlaroService, useValue: klaroServiceSpy},
46+
]
47+
});
48+
service = TestBed.inject(DatadogRumService);
49+
store = TestBed.inject(MockStore);
50+
memoizedSelector = store.overrideSelector(service.datadogRumStateSelector, initialState.datadogRum);
51+
klaroService = TestBed.inject(KlaroService);
52+
53+
spyOn(store, 'dispatch');
54+
});
55+
56+
it('should be created', () => {
57+
expect(service).toBeTruthy();
58+
});
59+
60+
it('should dispatch setDatadogRumStatusAction with isInitialized and isRunning true when datadog cookie is accepted', () => {
61+
memoizedSelector.setResult({isInitialized: false, isRunning: false});
62+
store.refreshState();
63+
consentsAccepted.datadog = true;
64+
environment.datadogRum = datadogRumEnvironmentOptions;
65+
service.initDatadogRum();
66+
expect(store.dispatch).toHaveBeenCalledWith(new setDatadogRumStatusAction({
67+
isInitialized: true,
68+
isRunning: true
69+
}));
70+
});
71+
72+
it('should dispatch setDatadogRumStatusAction with isRunning true when datadog cookie is accepted and isInitialized is true', () => {
73+
memoizedSelector.setResult({isInitialized: true, isRunning: false});
74+
store.refreshState();
75+
consentsAccepted.datadog = true;
76+
environment.datadogRum = datadogRumEnvironmentOptions;
77+
service.initDatadogRum();
78+
expect(store.dispatch).toHaveBeenCalledWith(new setDatadogRumStatusAction({
79+
isRunning: true
80+
}));
81+
});
82+
83+
it('should dispatch setDatadogRumStatusAction with isRunning false when datadog cookie is not accepted', () => {
84+
memoizedSelector.setResult({isInitialized: true, isRunning: true});
85+
store.refreshState();
86+
consentsAccepted.datadog = false;
87+
service.initDatadogRum();
88+
expect(store.dispatch).toHaveBeenCalledWith(new setDatadogRumStatusAction({
89+
isRunning: false
90+
}));
91+
});
92+
93+
});
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { Injectable } from '@angular/core';
2+
import { environment } from '../../../environments/environment';
3+
import { datadogRum } from '@datadog/browser-rum';
4+
import { CookieConsents, KlaroService } from '../cookies/klaro.service';
5+
import { BehaviorSubject, Observable } from 'rxjs';
6+
import { createSelector, Store } from '@ngrx/store';
7+
import { setDatadogRumStatusAction } from './datadog-rum.actions';
8+
import { DatadogRumState } from './datadog-rum.reducer';
9+
import { distinctUntilChanged, take } from 'rxjs/operators';
10+
import { coreSelector } from '../../core/core.selectors';
11+
import { CoreState } from '../../core/core-state.model';
12+
13+
@Injectable({
14+
providedIn: 'root'
15+
})
16+
export class DatadogRumService {
17+
18+
consentsUpdates$: BehaviorSubject<CookieConsents>;
19+
datadogRumStateSelector = createSelector(coreSelector, (state: CoreState) => state.datadogRum);
20+
21+
constructor(
22+
private klaroService: KlaroService,
23+
private store: Store
24+
) {
25+
}
26+
27+
initDatadogRum() {
28+
this.klaroService.watchConsentUpdates();
29+
this.consentsUpdates$ = this.klaroService.consentsUpdates$;
30+
this.consentsUpdates$.subscribe(savedPreferences => {
31+
this.getDatadogRumState().subscribe((state) => {
32+
if (savedPreferences?.datadog &&
33+
environment.datadogRum?.clientToken && environment.datadogRum?.applicationId &&
34+
environment.datadogRum?.service && environment.datadogRum?.env) {
35+
if (!state.isInitialized) {
36+
this.store.dispatch(new setDatadogRumStatusAction({
37+
isInitialized: true,
38+
isRunning: true
39+
}));
40+
datadogRum.init(environment.datadogRum);
41+
} else if (!state.isRunning) {
42+
this.store.dispatch(new setDatadogRumStatusAction({
43+
isRunning: true
44+
}));
45+
datadogRum.startSessionReplayRecording();
46+
}
47+
} else {
48+
datadogRum.stopSessionReplayRecording();
49+
this.store.dispatch(new setDatadogRumStatusAction({
50+
isRunning: false
51+
}));
52+
}
53+
});
54+
});
55+
}
56+
57+
58+
getDatadogRumState(): Observable<DatadogRumState> {
59+
return this.store
60+
.select(this.datadogRumStateSelector)
61+
.pipe(
62+
distinctUntilChanged(),
63+
take(1),
64+
);
65+
}
66+
}
67+

0 commit comments

Comments
 (0)