Skip to content

Commit

Permalink
Enable error management on page running a process
Browse files Browse the repository at this point in the history
  • Loading branch information
Quetzacoalt91 committed Feb 4, 2025
1 parent b81f5f0 commit a5a2515
Show file tree
Hide file tree
Showing 11 changed files with 73 additions and 37 deletions.
2 changes: 1 addition & 1 deletion _dev/src/ts/api/RequestHandler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import baseApi from './baseApi';
import { ApiError, ApiResponse, ApiResponseAction, ApiResponseUnknown } from '../types/apiTypes';
import { ApiResponse, ApiResponseAction, ApiResponseUnknown } from '../types/apiTypes';
import Hydration from '../utils/Hydration';
import { AxiosError } from 'axios';
import { toApiError, toApiResponseAction } from './axiosError';
Expand Down
11 changes: 5 additions & 6 deletions _dev/src/ts/api/axiosError.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AxiosError } from "axios";
import { ApiError, ApiResponseAction } from "../types/apiTypes";
import { AxiosError } from 'axios';
import { ApiError, ApiResponseAction } from '../types/apiTypes';

export const toApiError = (error: AxiosError): ApiError => ({
code: error.status,
Expand All @@ -24,11 +24,10 @@ export const toApiResponseAction = (error: AxiosError): ApiResponseAction => ({

export const isHttpErrorCode = (code?: number): boolean => {
return typeof code === 'number' && code >= 300 && code.toString().length === 3;
}
const formatResponseContents = (error: AxiosError): string|undefined => {
};

const formatResponseContents = (error: AxiosError): string | undefined => {
return typeof error.response?.data === 'string'
? error.response?.data
: JSON.stringify(error.response?.data);
};


16 changes: 10 additions & 6 deletions _dev/src/ts/api/sentryApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,32 +37,36 @@ Sentry.init({
* This function attaches log files, captures a custom event, and optionally sends user feedback with an associated event ID.
*
* @param {string} message - The message to describe the feedback or error.
* @param {Logs} [logs={}] - An object containing optional logs, warnings, and errors to attach.
* @param {{Logs, Map}} attachments - An object containing optional logs, warnings and errors messages and other data to attach.
* @param {Feedback} [feedback={}] - An object containing optional user feedback fields such as email and comments.
* @param {SeverityLevel} [level='error'] - The severity level of the event (e.g., 'info', 'warning', 'error').
*/
export function sendUserFeedback(
message: string,
logs: Logs = {},
attachments: { logs: Logs; other: Map<string, string> },
feedback: Feedback = {},
level: SeverityLevel = 'error'
) {
const attachments: { key: LogsFields; filename: string }[] = [
const logsAttachments: { key: LogsFields; filename: string }[] = [
{ key: LogsFields.LOGS, filename: 'logs.txt' },
{ key: LogsFields.WARNINGS, filename: 'summary_warnings.txt' },
{ key: LogsFields.ERRORS, filename: 'summary_errors.txt' }
];

attachments.forEach(({ key, filename }) => {
if (logs[key]) {
logsAttachments.forEach(({ key, filename }) => {
if (attachments.logs[key]) {
Sentry.getCurrentScope().addAttachment({
filename,
data: logs[key],
data: attachments.logs[key],
contentType: 'text/plain'
});
}
});

attachments.other.forEach((data, filename) => {
Sentry.getCurrentScope().addAttachment({ filename, data });
});

const maskedUrl = maskSensitiveInfoInUrl(window.location.href, adminDir);

const eventId = Sentry.captureEvent({
Expand Down
14 changes: 14 additions & 0 deletions _dev/src/ts/components/ErrorPageBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { isHttpErrorCode } from '../api/axiosError';
import { ApiError } from '../types/apiTypes';

export default class ErrorPageBuilder {
public static readonly externalAdditionalContentsPanelId = 'log-additional-contents';

public constructor(private readonly errorElement: DocumentFragment) {}

/**
Expand Down Expand Up @@ -43,4 +45,16 @@ export default class ErrorPageBuilder {
errorDescriptionElement.innerHTML = errorDetails.type;
}
}

/**
* Store the response contents on the DOM to keep it ready to send in the report.
*/
public updateResponseBlock(response: ApiError['additionalContents']): void {
const errorDescriptionElement = this.errorElement.getElementById(
ErrorPageBuilder.externalAdditionalContentsPanelId
);
if (errorDescriptionElement && response) {
errorDescriptionElement.textContent = response;
}
}
}
24 changes: 15 additions & 9 deletions _dev/src/ts/components/LogsViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,29 +21,29 @@ export default class LogsViewer extends ComponentAbstract implements DomLifecycl
LOG_BEFORE_SCROLL: 120 // The number of logs to process before automatically scrolling to the bottom.
};

#formId = 'form-logs-download-button';
readonly #formId = 'form-logs-download-button';

#templateLogLine = this.queryElement<HTMLTemplateElement>(
readonly #templateLogLine = this.queryElement<HTMLTemplateElement>(
'#log-line',
'Template log line not found'
);

#logsSummary = this.queryElement<HTMLDivElement>(
readonly #logsSummary = this.queryElement<HTMLDivElement>(
'[data-slot-component="summary"]',
'Logs summary not found'
);

#templateSummary = this.queryElement<HTMLTemplateElement>(
readonly #templateSummary = this.queryElement<HTMLTemplateElement>(
'#log-summary',
'Template summary not found'
);

#logsScroll = this.queryElement<HTMLDivElement>(
readonly #logsScroll = this.queryElement<HTMLDivElement>(
'[data-slot-component="scroll"]',
'Logs scroll not found'
);

#additionalContentsPanel = this.queryElement<HTMLPreElement>(
readonly #additionalContentsPanel = this.queryElement<HTMLPreElement>(
'#log-additional-contents',
'Panel of additional contents not found'
);
Expand Down Expand Up @@ -102,19 +102,25 @@ export default class LogsViewer extends ComponentAbstract implements DomLifecycl
};

public addError = (error: ApiError): void => {
const detailedError = document.getElementById(ErrorPage.templateId)?.content?.querySelector(`.error-page__desc .error-page__desc-${isHttpErrorCode(error.code) ? error.code : error.type}`);
const detailedError = (
document.getElementById(ErrorPage.templateId) as HTMLTemplateElement | null
)?.content?.querySelector(
`.error-page__desc .error-page__desc-${isHttpErrorCode(error.code) ? error.code : error.type}`
);
if (detailedError) {
this.addLogs([`ERROR - ${detailedError.textContent}`]);
}
this.addLogs([`ERROR - HTTP request failed: Type: ${error.type || 'N/A'} - HTTP Code ${error.code || 'N/A'}`]);
this.addLogs([
`ERROR - HTTP request failed. Type: ${error.type ?? 'N/A'} - HTTP Code ${error.code ?? 'N/A'}`
]);

// Contents is added on the DOM in a hidden panel in order to:
// - Display it if requested in the future
// - Have a place to store it for the error report without displaying random contents in the logs
if (error.additionalContents) {
this.#additionalContentsPanel.innerText = error.additionalContents;
}
}
};

/**
* @private
Expand Down
14 changes: 12 additions & 2 deletions _dev/src/ts/dialogs/SendErrorReportDialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Feedback, FeedbackFields, Logs } from '../types/sentryApi';
import { logStore } from '../store/LogStore';
import { formatLogsMessages } from '../utils/logsUtils';
import DialogAbstract from './DialogAbstract';
import ErrorPageBuilder from '../components/ErrorPageBuilder';

export default class SendErrorReportDialog extends DialogAbstract {
protected readonly formId = 'form-error-feedback';
Expand Down Expand Up @@ -36,10 +37,19 @@ export default class SendErrorReportDialog extends DialogAbstract {
onSubmit = async (event: SubmitEvent) => {
event.preventDefault();

const logs = this.#getLogs();
const attachments = {
logs: this.#getLogs(),
other: new Map<string, string>()
};
const responseContents = document.getElementById(
ErrorPageBuilder.externalAdditionalContentsPanelId
)?.textContent;
if (responseContents) {
attachments.other.set('response_raw.txt', responseContents);
}
const feedback = this.#getFeedback(event.target as HTMLFormElement);

sendUserFeedback(this.#lastErrorMessage, logs, feedback);
sendUserFeedback(this.#lastErrorMessage, attachments, feedback);

this.dispatchDialogContainerOkEvent(event);
};
Expand Down
13 changes: 2 additions & 11 deletions _dev/src/ts/pages/ErrorPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,7 @@ export default class ErrorPage extends PageAbstract {
pageBuilder.updateId(event.detail.type);
pageBuilder.updateLeftColumn(event.detail.code);
pageBuilder.updateDescriptionBlock(event.detail);

// Store the contents in the logs so it can be used in the error reporting modal
if (event.detail.additionalContents) {
logStore.addLog({
severity: Severity.SUCCESS,
height: 0,
offsetTop: 0,
message: event.detail.additionalContents
});
}
pageBuilder.updateResponseBlock(event.detail.additionalContents);

// Finally, append the result on the page
const targetElementToUpdate = document.getElementById(
Expand All @@ -96,7 +87,7 @@ export default class ErrorPage extends PageAbstract {
severity: Severity.ERROR,
height: 0,
offsetTop: 0,
message: `HTTP request failed: Route ${route ?? 'N/A'} - Type: ${event.detail.type ?? 'N/A'} - Code ${event.detail.code ?? 'N/A'}`
message: `HTTP request failed. Route ${route ?? 'N/A'} - Type: ${event.detail.type ?? 'N/A'} - HTTP Code ${event.detail.code ?? 'N/A'}`
});

// Enable events and page features
Expand Down
11 changes: 10 additions & 1 deletion _dev/tests/components/ErrorPageBuilder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ describe('ErrorPageBuilder', () => {
</div>
</div>
</div>
</div>`);
</div>
<pre id="log-additional-contents" class="hidden"></pre>`);
errorPageBuilder = new ErrorPageBuilder(errorElement);
});

Expand Down Expand Up @@ -151,4 +153,11 @@ describe('ErrorPageBuilder', () => {
errorPageBuilder.updateDescriptionBlock({ code: 999, type: 'CUSTOM_ERROR' });
expect(errorElement.querySelector('.error-page__desc')!.innerHTML).toBe('CUSTOM_ERROR');
});

test('updateResponseBlock should get the response contents', () => {
errorPageBuilder.updateResponseBlock('{"Some data": "Oh no!"}');
expect(errorElement.getElementById('log-additional-contents')?.textContent).toBe(
'{"Some data": "Oh no!"}'
);
});
});
1 change: 1 addition & 0 deletions _dev/tests/components/LogsViewer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ describe('LogsViewer', () => {
<div data-slot-component="list" class="logs__list"></div>
</div>
<div data-slot-component="summary" class="logs__summaries"></div>
<pre id="log-additional-contents" class="hidden"></pre>
</div>
<template id="log-line">
<div class="logs__line">
Expand Down
2 changes: 1 addition & 1 deletion views/templates/components/logs-viewer.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
</div>
<div data-slot-component="summary" class="logs__summaries"></div>
<div id="{{ download_logs_parent_id }}"></div>
<pre id="log-additional-contents"></pre>
<pre id="log-additional-contents" class="hidden"></pre>
</div>
2 changes: 2 additions & 0 deletions views/templates/pages/errors/generic.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
</ul>
</div>
</div>

<pre id="log-additional-contents" class="hidden"></pre>
{% endblock %}

{% block button %}
Expand Down

0 comments on commit a5a2515

Please sign in to comment.