Skip to content

Commit ffd4965

Browse files
authored
139 show copyable and pastable error message to user when they encounter an issue (#148)
* feat: added error handler extension * feat: add error page and error handler * feat: added redirect * fix: remove test throw error * fix: ngZone bug
1 parent 27a6da6 commit ffd4965

File tree

7 files changed

+121
-12
lines changed

7 files changed

+121
-12
lines changed

src/app/CustomErrorHandler.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,35 @@
1-
import { Injectable } from '@angular/core';
1+
import { Injectable, Injector, NgZone } from '@angular/core';
22
import { Router } from '@angular/router';
33
import { SentryErrorHandler } from '@sentry/angular';
44
import { environment } from 'src/environments/environment';
55
import { TaskManagerService } from './services/task-manager.service';
66
import { UserStateService } from './services/user-state-service';
7-
import { IErrorNavigationState } from './pages/participant/error-page/error-page.component';
7+
import { IErrorNavigationState } from './pages/error-page/error-page.component';
88

99
@Injectable()
1010
export default class CustomErrorHandler extends SentryErrorHandler {
1111
constructor(
1212
private _router: Router,
1313
private taskManager: TaskManagerService,
14-
private userStateService: UserStateService
14+
private userStateService: UserStateService,
15+
private injector: Injector
1516
) {
1617
super();
1718
}
1819

1920
handleError(error: string | Error): void {
20-
this._router.navigate(['/task-error'], {
21-
state: {
22-
taskIndex: this.taskManager?.currentStudyTask?.taskOrder,
23-
studyId: this.taskManager?.currentStudyTask?.studyId,
24-
stackTrace: error instanceof Error ? error.stack : error,
25-
userId: this.userStateService.currentlyLoggedInUserId,
26-
} as IErrorNavigationState,
21+
const ngZone = this.injector.get(NgZone);
22+
ngZone.run(() => {
23+
this._router.navigate(['task-error'], {
24+
state: {
25+
taskIndex: this.taskManager?.currentStudyTask?.taskOrder,
26+
studyId: this.taskManager?.currentStudyTask?.studyId,
27+
stackTrace: error instanceof Error ? error.stack : error,
28+
userId: this.userStateService.currentlyLoggedInUserId,
29+
} as IErrorNavigationState,
30+
});
2731
});
32+
2833
if (environment.production) {
2934
super.handleError(error);
3035
}

src/app/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { OrganizationMemberModule } from './pages/organization-member/organizati
2727
import { SharedModule } from './pages/shared/shared.module';
2828
import { SnackbarComponent } from './services/snackbar/snackbar.component';
2929
import CustomErrorHandler from './CustomErrorHandler';
30+
import { ErrorPageComponent } from './pages/error-page/error-page.component';
3031

3132
export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
3233
return new TranslateHttpLoader(http, '../assets/translate/', '.json');
@@ -35,6 +36,7 @@ export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
3536
@NgModule({
3637
declarations: [
3738
AppComponent,
39+
ErrorPageComponent,
3840
LoginComponent,
3941
CrowdSourceLoginComponent,
4042
ConfirmationComponent,
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<div style="width: 100%; min-height: 100vh; padding: 4rem 0">
2+
<div class="flex-column-center">
3+
<div style="width: 50%">
4+
<h6 style="font-size: 2rem; color: salmon" class="mb-4">We encountered an error.</h6>
5+
<div style="font-size: 1.5rem; line-height: normal" class="mb-4">
6+
While running the platform, we ran into an error. In order to report this and help us fix it as fast as
7+
possible, please do the following:
8+
</div>
9+
<div class="steps-container">
10+
<div class="number-container text">1</div>
11+
<div class="content-container">
12+
<span class="my-3" style="font-size: 1.5rem; line-height: normal">
13+
The relevant information and error is displayed below. Please click the
14+
<span style="color: #3f51b5">Copy Error</span> button to copy it to your clipboard.
15+
</span>
16+
<div style="background-color: rgb(233, 233, 233); padding: 1rem; border-radius: 8px">
17+
<div style="text-align: start" class="mb-2">
18+
<button mat-flat-button [cdkCopyToClipboard]="copyableErrString" color="primary">
19+
<span>Copy Error</span>
20+
<mat-icon class="ml-3">content_copy</mat-icon>
21+
</button>
22+
</div>
23+
<div style="color: salmon; max-height: 200px; overflow-y: auto; white-space: pre-line">
24+
{{ copyableErrString }}
25+
</div>
26+
</div>
27+
</div>
28+
</div>
29+
<div class="steps-container">
30+
<div class="number-container text">2</div>
31+
<div class="content-container">
32+
<span class="my-2" style="font-size: 1.5rem; line-height: normal">
33+
Paste the error into an email and send it to
34+
<a href="mailto:[email protected]">sharplab.neuro&#64;mcgill.ca</a>
35+
</span>
36+
</div>
37+
</div>
38+
<div class="steps-container">
39+
<div class="number-container text">3</div>
40+
<div class="content-container">
41+
<span class="my-2" style="font-size: 1.5rem; line-height: normal"> Go back to the main menu.</span>
42+
<span style="font-size: 1.5rem; line-height: normal">
43+
Thank for your participation and your assistance!
44+
</span>
45+
<div class="mt-2">
46+
<button mat-flat-button color="primary" (click)="navigateToMain()">Back to main menu</button>
47+
</div>
48+
</div>
49+
</div>
50+
</div>
51+
</div>
52+
</div>

src/app/pages/error-page/error-page.component.scss

Whitespace-only changes.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { Component, OnInit } from '@angular/core';
2+
import { Router } from '@angular/router';
3+
import { SessionStorageService } from '../../services/sessionStorage.service';
4+
import { SnackbarService } from '../../services/snackbar/snackbar.service';
5+
import { Role } from 'src/app/models/enums';
6+
import { UserStateService } from 'src/app/services/user-state-service';
7+
8+
export interface IErrorNavigationState {
9+
studyId?: number;
10+
taskIndex?: number;
11+
stackTrace?: string;
12+
userId?: string;
13+
}
14+
15+
@Component({
16+
selector: 'app-error-page',
17+
templateUrl: './error-page.component.html',
18+
styleUrls: ['./error-page.component.scss', '../participant/final-page/final-page.component.scss'],
19+
})
20+
export class ErrorPageComponent implements OnInit {
21+
constructor(private _router: Router, private _userStateService: UserStateService) {
22+
const params = this._router.getCurrentNavigation()?.extras?.state as IErrorNavigationState;
23+
this.copyableErrString = `User ID: ${params?.userId}\nStudy ID: ${params?.studyId}\nTask Index: ${params?.taskIndex}\n\n${params?.stackTrace}`;
24+
this.copyableErrString = this.copyableErrString.trim();
25+
}
26+
27+
copyableErrString: string = '';
28+
29+
ngOnInit(): void {}
30+
31+
navigateToMain() {
32+
if (this._userStateService.isCrowdsourcedUser) {
33+
if (this._userStateService.currentlyRunningStudyId) {
34+
this._router.navigateByUrl(
35+
`crowdsource-participant?studyid=${this._userStateService.currentlyRunningStudyId}`
36+
);
37+
} else {
38+
this._router.navigate([`crowdsource-participant`]);
39+
}
40+
} else if (this._userStateService.userIsAdmin) {
41+
this._router.navigate([`admin-dashboard`]);
42+
} else if (this._userStateService.userIsParticipant) {
43+
this._router.navigate([`participant-dashboard`]);
44+
} else if (this._userStateService.userIsGuest || this._userStateService.userIsOrgMember) {
45+
this._router.navigate([`organization-member-dashboard`]);
46+
} else {
47+
throw new Error('Unsupported user role!');
48+
}
49+
}
50+
}

src/app/routing/app-routing.module.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { ResetPasswordLoginComponent } from '../pages/landing-page/forgot-passwo
1212
import { StudyBackgroundComponent } from '../pages/landing-page/study-background/study-background.component';
1313
import { NotFoundComponent } from '../pages/landing-page/not-found/not-found.component';
1414
import { BlankComponent } from '../pages/tasks/blank/blank.component';
15-
import { ErrorPageComponent } from '../pages/participant/error-page/error-page.component';
15+
import { ErrorPageComponent } from '../pages/error-page/error-page.component';
1616

1717
const routes: Routes = [
1818
{

src/app/services/task-manager.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { StudyTask } from '../models/StudyTask';
2424
import { UserStateService } from './user-state-service';
2525
import { CrowdSourcedUserService } from './crowdsourced-user.service';
2626
import { snapshotToStudyTasks } from './utils';
27-
import { IErrorNavigationState } from '../pages/participant/error-page/error-page.component';
27+
import { IErrorNavigationState } from '../pages/error-page/error-page.component';
2828

2929
@Injectable({
3030
providedIn: 'root',

0 commit comments

Comments
 (0)