diff --git a/_dev/src/ts/api/RequestHandler.ts b/_dev/src/ts/api/RequestHandler.ts index 06e77ee70..e2dca0c6a 100644 --- a/_dev/src/ts/api/RequestHandler.ts +++ b/_dev/src/ts/api/RequestHandler.ts @@ -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'; diff --git a/_dev/src/ts/api/axiosError.ts b/_dev/src/ts/api/axiosError.ts index 64d04c3a5..a2ce06de6 100644 --- a/_dev/src/ts/api/axiosError.ts +++ b/_dev/src/ts/api/axiosError.ts @@ -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, @@ -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); }; - - diff --git a/_dev/src/ts/api/sentryApi.ts b/_dev/src/ts/api/sentryApi.ts index 9182b7b99..af449bcb6 100644 --- a/_dev/src/ts/api/sentryApi.ts +++ b/_dev/src/ts/api/sentryApi.ts @@ -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 }, 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({ diff --git a/_dev/src/ts/components/ErrorPageBuilder.ts b/_dev/src/ts/components/ErrorPageBuilder.ts index 4b80b9fb6..04665c5a8 100644 --- a/_dev/src/ts/components/ErrorPageBuilder.ts +++ b/_dev/src/ts/components/ErrorPageBuilder.ts @@ -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) {} /** @@ -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; + } + } } diff --git a/_dev/src/ts/components/LogsViewer.ts b/_dev/src/ts/components/LogsViewer.ts index 5de7d000f..9e23675a9 100644 --- a/_dev/src/ts/components/LogsViewer.ts +++ b/_dev/src/ts/components/LogsViewer.ts @@ -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( + readonly #templateLogLine = this.queryElement( '#log-line', 'Template log line not found' ); - #logsSummary = this.queryElement( + readonly #logsSummary = this.queryElement( '[data-slot-component="summary"]', 'Logs summary not found' ); - #templateSummary = this.queryElement( + readonly #templateSummary = this.queryElement( '#log-summary', 'Template summary not found' ); - #logsScroll = this.queryElement( + readonly #logsScroll = this.queryElement( '[data-slot-component="scroll"]', 'Logs scroll not found' ); - #additionalContentsPanel = this.queryElement( + readonly #additionalContentsPanel = this.queryElement( '#log-additional-contents', 'Panel of additional contents not found' ); @@ -102,11 +102,17 @@ 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 @@ -114,7 +120,7 @@ export default class LogsViewer extends ComponentAbstract implements DomLifecycl if (error.additionalContents) { this.#additionalContentsPanel.innerText = error.additionalContents; } - } + }; /** * @private diff --git a/_dev/src/ts/dialogs/SendErrorReportDialog.ts b/_dev/src/ts/dialogs/SendErrorReportDialog.ts index b367ba3fc..ea4e9cd14 100644 --- a/_dev/src/ts/dialogs/SendErrorReportDialog.ts +++ b/_dev/src/ts/dialogs/SendErrorReportDialog.ts @@ -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'; @@ -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() + }; + 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); }; diff --git a/_dev/src/ts/pages/ErrorPage.ts b/_dev/src/ts/pages/ErrorPage.ts index e14da0023..f7f52cd95 100644 --- a/_dev/src/ts/pages/ErrorPage.ts +++ b/_dev/src/ts/pages/ErrorPage.ts @@ -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( @@ -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 diff --git a/_dev/tests/components/ErrorPageBuilder.test.ts b/_dev/tests/components/ErrorPageBuilder.test.ts index ec8a511e8..0a0474a4f 100644 --- a/_dev/tests/components/ErrorPageBuilder.test.ts +++ b/_dev/tests/components/ErrorPageBuilder.test.ts @@ -91,7 +91,9 @@ describe('ErrorPageBuilder', () => { - `); + + + `); errorPageBuilder = new ErrorPageBuilder(errorElement); }); @@ -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!"}' + ); + }); }); diff --git a/_dev/tests/components/LogsViewer.test.ts b/_dev/tests/components/LogsViewer.test.ts index afe923284..299ff6dca 100644 --- a/_dev/tests/components/LogsViewer.test.ts +++ b/_dev/tests/components/LogsViewer.test.ts @@ -26,6 +26,7 @@ describe('LogsViewer', () => {
+