Skip to content

Commit 5a41a24

Browse files
authored
Feature/eslint pipeline Fixed all the lint errors and updated sentry (#396)
* chore(eslint): added updates to eslint for the pipeline * chore(linting): fixed some linting * chore(error-updates): added hints to the sentry
1 parent ae9ac64 commit 5a41a24

File tree

12 files changed

+169
-55
lines changed

12 files changed

+169
-55
lines changed

src/app/app.config.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { provideRouter, withInMemoryScrolling } from '@angular/router';
1313

1414
import { STATES } from '@core/constants';
1515
import { APPLICATION_INITIALIZATION_PROVIDER } from '@core/factory/application.initialization.factory';
16+
import { SENTRY_PROVIDER } from '@core/factory/sentry.factory';
1617
import { provideTranslation } from '@core/helpers';
1718

1819
import { authInterceptor, errorInterceptor, viewOnlyInterceptor } from './core/interceptors';
@@ -23,9 +24,15 @@ import * as Sentry from '@sentry/angular';
2324

2425
export const appConfig: ApplicationConfig = {
2526
providers: [
26-
provideZoneChangeDetection({ eventCoalescing: true }),
27-
provideRouter(routes, withInMemoryScrolling({ scrollPositionRestoration: 'top', anchorScrolling: 'enabled' })),
28-
provideStore(STATES, withNgxsReduxDevtoolsPlugin({ disabled: false })),
27+
APPLICATION_INITIALIZATION_PROVIDER,
28+
ConfirmationService,
29+
{
30+
provide: ErrorHandler,
31+
useFactory: () => Sentry.createErrorHandler({ showDialog: false }),
32+
},
33+
importProvidersFrom(TranslateModule.forRoot(provideTranslation())),
34+
MessageService,
35+
provideAnimations(),
2936
providePrimeNG({
3037
theme: {
3138
preset: CustomPreset,
@@ -38,16 +45,10 @@ export const appConfig: ApplicationConfig = {
3845
},
3946
},
4047
}),
41-
provideAnimations(),
4248
provideHttpClient(withInterceptors([authInterceptor, viewOnlyInterceptor, errorInterceptor])),
43-
importProvidersFrom(TranslateModule.forRoot(provideTranslation())),
44-
ConfirmationService,
45-
MessageService,
46-
47-
APPLICATION_INITIALIZATION_PROVIDER,
48-
{
49-
provide: ErrorHandler,
50-
useFactory: () => Sentry.createErrorHandler({ showDialog: false }),
51-
},
49+
provideRouter(routes, withInMemoryScrolling({ scrollPositionRestoration: 'top', anchorScrolling: 'enabled' })),
50+
provideStore(STATES, withNgxsReduxDevtoolsPlugin({ disabled: false })),
51+
provideZoneChangeDetection({ eventCoalescing: true }),
52+
SENTRY_PROVIDER,
5253
],
5354
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { TestBed } from '@angular/core/testing';
2+
3+
import { SENTRY_PROVIDER, SENTRY_TOKEN } from './sentry.factory';
4+
5+
import * as Sentry from '@sentry/angular';
6+
7+
describe('Factory: Sentry', () => {
8+
beforeEach(() => {
9+
TestBed.configureTestingModule({
10+
providers: [SENTRY_PROVIDER],
11+
});
12+
});
13+
14+
it('should provide the Sentry module via the injection token', () => {
15+
const provided = TestBed.inject(SENTRY_TOKEN);
16+
expect(provided).toBe(Sentry);
17+
expect(typeof provided.captureException).toBe('function');
18+
});
19+
});
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { InjectionToken } from '@angular/core';
2+
3+
import * as Sentry from '@sentry/angular';
4+
5+
/**
6+
* Injection token used to provide the Sentry module via Angular's dependency injection system.
7+
*
8+
* This token represents the entire Sentry module (`@sentry/angular`), allowing you to inject
9+
* and use Sentry APIs (e.g., `captureException`, `init`, `setUser`, etc.) in Angular services
10+
* or components.
11+
*
12+
* @example
13+
* ```ts
14+
* const Sentry = inject(SENTRY_TOKEN);
15+
* Sentry.captureException(new Error('Something went wrong'));
16+
* ```
17+
*/
18+
export const SENTRY_TOKEN = new InjectionToken<typeof Sentry>('Sentry');
19+
20+
/**
21+
* Angular provider that binds the `SENTRY_TOKEN` to the actual `@sentry/angular` module.
22+
*
23+
* Use this provider in your module or application configuration to make Sentry injectable.
24+
*
25+
* @example
26+
* ```ts
27+
* providers: [
28+
* SENTRY_PROVIDER,
29+
* ]
30+
* ```
31+
*
32+
* Inject the Sentry module via the factory token
33+
* private readonly Sentry = inject(SENTRY_TOKEN);
34+
*
35+
* throwError(): void {
36+
* try {
37+
* throw new Error('Test error for Sentry capture');
38+
* } catch (error) {
39+
* Send the error to Sentry
40+
* this.Sentry.captureException(error);
41+
* }
42+
* }
43+
*
44+
* @see SENTRY_TOKEN
45+
*/
46+
export const SENTRY_PROVIDER = {
47+
provide: SENTRY_TOKEN,
48+
useValue: Sentry,
49+
};

src/app/core/handlers/global-error.handler.ts

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/app/core/handlers/index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/app/core/store/user/user.state.ts

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -268,23 +268,21 @@ export class UserState {
268268
};
269269
const apiRequest = UserMapper.toAcceptedTermsOfServiceRequest(updatePayload);
270270

271-
return this.userService
272-
.updateUserAcceptedTermsOfService(currentUser.id, apiRequest)
273-
.pipe(
274-
tap((response: User): void => {
275-
if (response.acceptedTermsOfService) {
276-
ctx.patchState({
277-
currentUser: {
278-
...state.currentUser,
279-
data: {
280-
...currentUser,
281-
acceptedTermsOfService: true,
282-
},
271+
return this.userService.updateUserAcceptedTermsOfService(currentUser.id, apiRequest).pipe(
272+
tap((response: User): void => {
273+
if (response.acceptedTermsOfService) {
274+
ctx.patchState({
275+
currentUser: {
276+
...state.currentUser,
277+
data: {
278+
...currentUser,
279+
acceptedTermsOfService: true,
283280
},
284-
});
285-
}
286-
})
287-
);
281+
},
282+
});
283+
}
284+
})
285+
);
288286
}
289287

290288
@Action(ClearCurrentUser)

src/app/features/collections/services/collections-query-sync.service.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { effect, inject, Injectable, signal } from '@angular/core';
44
import { toSignal } from '@angular/core/rxjs-interop';
55
import { ActivatedRoute, Router } from '@angular/router';
66

7+
import { SENTRY_TOKEN } from '@core/factory/sentry.factory';
78
import { collectionsSortOptions } from '@osf/features/collections/constants';
89
import { queryParamsKeys } from '@osf/features/collections/constants/query-params-keys.const';
910
import { CollectionQueryParams } from '@osf/features/collections/models';
@@ -13,6 +14,7 @@ import { SetPageNumber } from '@shared/stores/collections/collections.actions';
1314

1415
@Injectable()
1516
export class CollectionsQuerySyncService {
17+
private readonly Sentry = inject(SENTRY_TOKEN);
1618
private readonly router = inject(Router);
1719
private readonly route = inject(ActivatedRoute);
1820

@@ -119,7 +121,7 @@ export class CollectionsQuerySyncService {
119121
const parsedFilters: CollectionsFilters = JSON.parse(activeFilters);
120122
this.handleParsedFilters(parsedFilters);
121123
} catch (error) {
122-
console.error('Error parsing activeFilters from URL:', error);
124+
this.Sentry.captureException(error);
123125
}
124126
}
125127

src/app/shared/components/addons/storage-item-selector/google-file-picker/google-file-picker.component.spec.ts

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { Store } from '@ngxs/store';
22

3-
import { of } from 'rxjs';
3+
import { Observable, of, throwError } from 'rxjs';
44

55
import { ComponentFixture, TestBed } from '@angular/core/testing';
66

7+
import { SENTRY_TOKEN } from '@core/factory/sentry.factory';
8+
79
import { GoogleFilePickerDownloadService } from './service/google-file-picker.download.service';
810
import { GoogleFilePickerComponent } from './google-file-picker.component';
911

@@ -12,11 +14,21 @@ import { OSFTestingModule, OSFTestingStoreModule } from '@testing/osf.testing.mo
1214
describe('Component: Google File Picker', () => {
1315
let component: GoogleFilePickerComponent;
1416
let fixture: ComponentFixture<GoogleFilePickerComponent>;
17+
1518
const googlePickerServiceSpy = {
16-
loadScript: jest.fn().mockReturnValue(of(void 0)),
17-
loadGapiModules: jest.fn().mockReturnValue(of(void 0)),
19+
loadScript: jest.fn((): Observable<void> => {
20+
return throwLoadScriptError ? throwError(() => new Error('loadScript failed')) : of(void 0);
21+
}),
22+
loadGapiModules: jest.fn((): Observable<void> => {
23+
return throwLoadGapiError ? throwError(() => new Error('loadGapiModules failed')) : of(void 0);
24+
}),
1825
};
1926

27+
let sentrySpy: any;
28+
29+
let throwLoadScriptError = false;
30+
let throwLoadGapiError = false;
31+
2032
const handleFolderSelection = jest.fn();
2133
const setDeveloperKey = jest.fn().mockReturnThis();
2234
const setAppId = jest.fn().mockReturnThis();
@@ -40,7 +52,16 @@ describe('Component: Google File Picker', () => {
4052
selectSnapshot: jest.fn().mockReturnValue('mock-token'),
4153
};
4254

55+
beforeEach(() => {
56+
throwLoadScriptError = false;
57+
throwLoadGapiError = false;
58+
jest.clearAllMocks();
59+
});
60+
4361
beforeAll(() => {
62+
throwLoadScriptError = false;
63+
throwLoadGapiError = false;
64+
4465
window.google = {
4566
picker: {
4667
Action: null,
@@ -54,8 +75,6 @@ describe('Component: Google File Picker', () => {
5475

5576
describe('isFolderPicker - true', () => {
5677
beforeEach(async () => {
57-
jest.clearAllMocks();
58-
5978
(window as any).google = {
6079
picker: {
6180
ViewId: {
@@ -86,6 +105,7 @@ describe('Component: Google File Picker', () => {
86105
await TestBed.configureTestingModule({
87106
imports: [OSFTestingModule, GoogleFilePickerComponent],
88107
providers: [
108+
{ provide: SENTRY_TOKEN, useValue: { captureException: jest.fn() } },
89109
{ provide: GoogleFilePickerDownloadService, useValue: googlePickerServiceSpy },
90110
{
91111
provide: Store,
@@ -94,6 +114,9 @@ describe('Component: Google File Picker', () => {
94114
],
95115
}).compileComponents();
96116

117+
sentrySpy = TestBed.inject(SENTRY_TOKEN);
118+
jest.spyOn(sentrySpy, 'captureException');
119+
97120
fixture = TestBed.createComponent(GoogleFilePickerComponent);
98121
component = fixture.componentInstance;
99122
fixture.componentRef.setInput('isFolderPicker', true);
@@ -108,6 +131,7 @@ describe('Component: Google File Picker', () => {
108131
it('should load script and then GAPI modules and initialize picker', () => {
109132
expect(googlePickerServiceSpy.loadScript).toHaveBeenCalled();
110133
expect(googlePickerServiceSpy.loadGapiModules).toHaveBeenCalled();
134+
expect(sentrySpy.captureException).not.toHaveBeenCalled();
111135

112136
expect(component.visible()).toBeTruthy();
113137
expect(component.isGFPDisabled()).toBeFalsy();
@@ -172,7 +196,6 @@ describe('Component: Google File Picker', () => {
172196

173197
describe('isFolderPicker - false', () => {
174198
beforeEach(async () => {
175-
jest.clearAllMocks();
176199
(window as any).google = {
177200
picker: {
178201
ViewId: {
@@ -203,6 +226,7 @@ describe('Component: Google File Picker', () => {
203226
await TestBed.configureTestingModule({
204227
imports: [OSFTestingStoreModule, GoogleFilePickerComponent],
205228
providers: [
229+
{ provide: SENTRY_TOKEN, useValue: { captureException: jest.fn() } },
206230
{ provide: GoogleFilePickerDownloadService, useValue: googlePickerServiceSpy },
207231
{
208232
provide: Store,
@@ -211,25 +235,48 @@ describe('Component: Google File Picker', () => {
211235
],
212236
}).compileComponents();
213237

238+
sentrySpy = TestBed.inject(SENTRY_TOKEN);
239+
jest.spyOn(sentrySpy, 'captureException');
240+
214241
fixture = TestBed.createComponent(GoogleFilePickerComponent);
215242
component = fixture.componentInstance;
216243
fixture.componentRef.setInput('isFolderPicker', false);
217244
fixture.componentRef.setInput('rootFolder', {
218245
itemId: 'root-folder-id',
219246
});
220247
fixture.componentRef.setInput('handleFolderSelection', jest.fn());
248+
});
249+
250+
it('should fail to load script', () => {
251+
throwLoadScriptError = true;
221252
fixture.detectChanges();
253+
expect(googlePickerServiceSpy.loadScript).toHaveBeenCalled();
254+
expect(sentrySpy.captureException).toHaveBeenCalledWith(Error('loadScript failed'), {
255+
tags: {
256+
feature: 'google-picker load',
257+
},
258+
});
259+
260+
expect(component.visible()).toBeFalsy();
261+
expect(component.isGFPDisabled()).toBeTruthy();
222262
});
223263

224-
it('should load script and then GAPI modules and initialize picker', () => {
264+
it('should load script and then failr GAPI modules', () => {
265+
throwLoadGapiError = true;
266+
fixture.detectChanges();
225267
expect(googlePickerServiceSpy.loadScript).toHaveBeenCalled();
226268
expect(googlePickerServiceSpy.loadGapiModules).toHaveBeenCalled();
227-
269+
expect(sentrySpy.captureException).toHaveBeenCalledWith(Error('loadGapiModules failed'), {
270+
tags: {
271+
feature: 'google-picker auth',
272+
},
273+
});
228274
expect(component.visible()).toBeFalsy();
229275
expect(component.isGFPDisabled()).toBeTruthy();
230276
});
231277

232278
it('should build the picker with correct configuration', () => {
279+
fixture.detectChanges();
233280
component.createPicker();
234281

235282
expect(window.google.picker.DocsView).toHaveBeenCalledWith('docs');

src/app/shared/components/addons/storage-item-selector/google-file-picker/google-file-picker.component.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Button } from 'primeng/button';
77
import { ChangeDetectionStrategy, Component, inject, input, OnInit, signal } from '@angular/core';
88

99
import { ENVIRONMENT } from '@core/constants/environment.token';
10+
import { SENTRY_TOKEN } from '@core/factory/sentry.factory';
1011
import { StorageItemModel } from '@osf/shared/models';
1112
import { GoogleFileDataModel } from '@osf/shared/models/files/google-file.data.model';
1213
import { GoogleFilePickerModel } from '@osf/shared/models/files/google-file.picker.model';
@@ -24,6 +25,7 @@ import { GoogleFilePickerDownloadService } from './service/google-file-picker.do
2425
changeDetection: ChangeDetectionStrategy.OnPush,
2526
})
2627
export class GoogleFilePickerComponent implements OnInit {
28+
private readonly Sentry = inject(SENTRY_TOKEN);
2729
readonly #translateService = inject(TranslateService);
2830
readonly #googlePicker = inject(GoogleFilePickerDownloadService);
2931
readonly #environment = inject(ENVIRONMENT);
@@ -68,12 +70,10 @@ export class GoogleFilePickerComponent implements OnInit {
6870
this.#initializePicker();
6971
this.#loadOauthToken();
7072
},
71-
// TODO add this error when the Sentry service is working
72-
//error: (err) => console.error('GAPI modules failed:', err),
73+
error: (err) => this.Sentry.captureException(err, { tags: { feature: 'google-picker auth' } }),
7374
});
7475
},
75-
// TODO add this error when the Sentry service is working
76-
// error: (err) => console.error('Script load failed:', err),
76+
error: (err) => this.Sentry.captureException(err, { tags: { feature: 'google-picker load' } }),
7777
});
7878
}
7979

src/app/shared/helpers/state-error.handler.spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ describe('Helper: State Error Handler', () => {
4646
},
4747
});
4848

49-
expect(Sentry.captureException).toHaveBeenCalledWith(error);
49+
expect(Sentry.captureException).toHaveBeenCalledWith(error, {
50+
tags: { feature: 'state error section: mySection', 'state.section': 'mySection' },
51+
});
5052
await expect(firstValueFrom(result$)).rejects.toThrow('Something went wrong');
5153
});
5254
});

0 commit comments

Comments
 (0)