From 4511245d3b3d007c5b183f642e21b3f6d938bf72 Mon Sep 17 00:00:00 2001 From: Dave Roberts Date: Wed, 29 Apr 2026 13:44:17 +0100 Subject: [PATCH 1/5] Fix error in logging.js There was an error in logging.js where the variable 'message' was being re-declared inside the else block, which caused issues when trying to upload the log message. This commit fixes that by using a different variable name for the formatted message before uploading it. --- src/frontend/js/lib/logging.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/frontend/js/lib/logging.js b/src/frontend/js/lib/logging.js index 4799b610a..5bf38d6ff 100644 --- a/src/frontend/js/lib/logging.js +++ b/src/frontend/js/lib/logging.js @@ -13,8 +13,8 @@ class Logging { if (this.allowLogging) { console.log(message) } else { - const message = this.formatMessage('log', ...message) - uploadMessage(message) + const msg = this.formatMessage('log', ...message) + uploadMessage(msg) } } @@ -22,8 +22,8 @@ class Logging { if (this.allowLogging) { console.info(message) } else { - const message = this.formatMessage('info', ...message) - uploadMessage(message) + const msg = this.formatMessage('info', ...message) + uploadMessage(msg) } } @@ -31,8 +31,8 @@ class Logging { if (this.allowLogging) { console.warn(message) } else { - const message = this.formatMessage('warn', ...message) - uploadMessage(message) + const msg = this.formatMessage('warn', ...message) + uploadMessage(msg) } } @@ -40,14 +40,15 @@ class Logging { if (this.allowLogging) { console.error(message) } else { - const message = this.formatMessage('error', ...message) - uploadMessage(message) + const msg = this.formatMessage('error', ...message) + uploadMessage(msg) } } formatMessage(type, ...message) { let output = type + ': '; for (let i = 0; i < message.length; i++) { + if(!message[i]) continue; if (typeof message[i] === 'object') { output += JSON.stringify(message[i]); } else { From eb367d932ef31320e9f85a0796c387d7026b1ef5 Mon Sep 17 00:00:00 2001 From: Dave Roberts Date: Fri, 1 May 2026 16:27:42 +0100 Subject: [PATCH 2/5] Fix error in logging and add unit test to ensure safety --- src/frontend/js/lib/logging.js | 36 +++++++---- src/frontend/js/lib/logging.test.js | 94 +++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 13 deletions(-) create mode 100644 src/frontend/js/lib/logging.test.js diff --git a/src/frontend/js/lib/logging.js b/src/frontend/js/lib/logging.js index 5bf38d6ff..135388ceb 100644 --- a/src/frontend/js/lib/logging.js +++ b/src/frontend/js/lib/logging.js @@ -1,47 +1,57 @@ -import { uploadMessage } from "util/scriptErrorHandler"; +import { MessageUploader } from "util/scriptErrorHandler/lib/MessageUploader"; +import { Uploader } from "util/upload/UploadControl"; -class Logging { - constructor() { +// Exported for testing +export class Logging { + /** + * Create a new Logging instance. + * @param {boolean} overrideAllowLogging True to force allowing of logging - used for testing + */ + constructor(overrideAllowLogging = false, uploader = new MessageUploader(new Uploader('/api/script_error', 'POST'))) { + this.uploader = uploader; this.allowLogging = window.test || location.hostname === 'localhost' || location.hostname === '127.0.0.1' || - location.hostname.endsWith('.peek.digitpaint.nl') + location.hostname.endsWith('.peek.digitpaint.nl'); + if(overrideAllowLogging !== undefined) { + this.allowLogging = overrideAllowLogging; + } } log(...message) { if (this.allowLogging) { - console.log(message) + console.log(...message) } else { const msg = this.formatMessage('log', ...message) - uploadMessage(msg) + this.uploader(msg) } } info(...message) { if (this.allowLogging) { - console.info(message) + console.info(...message) } else { const msg = this.formatMessage('info', ...message) - uploadMessage(msg) + this.uploader(msg) } } warn(...message) { if (this.allowLogging) { - console.warn(message) + console.warn(...message) } else { const msg = this.formatMessage('warn', ...message) - uploadMessage(msg) + this.uploader(msg) } } error(...message) { if (this.allowLogging) { - console.error(message) + console.error(...message) } else { const msg = this.formatMessage('error', ...message) - uploadMessage(msg) + this.uploader(msg) } } @@ -61,5 +71,5 @@ class Logging { } } -const logging = new Logging +const logging = new Logging(); export { logging } diff --git a/src/frontend/js/lib/logging.test.js b/src/frontend/js/lib/logging.test.js new file mode 100644 index 000000000..2bfaeeea5 --- /dev/null +++ b/src/frontend/js/lib/logging.test.js @@ -0,0 +1,94 @@ +import { describe, it, expect } from "@jest/globals"; +import { Logging } from "./logging"; + +describe("Logging", () => { + describe("Local logging", () => { + it("should log a message at log level", () => { + const logger = new Logging(true); + const consoleFn = jest.fn(); + console.log = consoleFn; + + logger.log("This is a log message"); + expect(consoleFn).toHaveBeenCalledWith("This is a log message"); + }); + + it("should log a message at info level", () => { + const logger = new Logging(true); + const consoleFn = jest.fn(); + console.info = consoleFn; + + logger.info("This is an info message"); + expect(consoleFn).toHaveBeenCalledWith("This is an info message"); + }); + + it("should log a message at warn level", () => { + const logger = new Logging(true); + const consoleFn = jest.fn(); + console.warn = consoleFn; + + logger.warn("This is a warning message"); + expect(consoleFn).toHaveBeenCalledWith("This is a warning message"); + }); + + it("should log a message at error level", () => { + const logger = new Logging(true); + const consoleFn = jest.fn(); + console.error = consoleFn; + + logger.error("This is an error message"); + expect(consoleFn).toHaveBeenCalledWith("This is an error message"); + }); + }); + + describe("Remote logging", () => { + it("should send a log message to the server", () => { + const uploadMessage = jest.fn(() => + Promise.resolve({ + ok: true, + json: () => Promise.resolve({ success: true }), + }) + ); + const logger = new Logging(false, uploadMessage); + + logger.log("This is a log message"); + expect(uploadMessage).toHaveBeenCalledWith("log: This is a log message"); + }); + + it("should send an info message to the server", () => { + const uploadMessage = jest.fn(() => + Promise.resolve({ + ok: true, + json: () => Promise.resolve({ success: true }), + }) + ); + const logger = new Logging(false, uploadMessage); + + logger.info("This is an info message"); + expect(uploadMessage).toHaveBeenCalledWith("info: This is an info message"); + }); + + it("should send a warning message to the server", () => { + const uploadMessage = jest.fn(() => + Promise.resolve({ + ok: true, + json: () => Promise.resolve({ success: true }), + }) + ); + const logger = new Logging(false, uploadMessage); + logger.warn("This is a warning message"); + expect(uploadMessage).toHaveBeenCalledWith("warn: This is a warning message"); + }); + + it("should send an error message to the server", () => { + const uploadMessage = jest.fn(() => + Promise.resolve({ + ok: true, + json: () => Promise.resolve({ success: true }), + }) + ); + const logger = new Logging(false, uploadMessage); + logger.error("This is an error message"); + expect(uploadMessage).toHaveBeenCalledWith("error: This is an error message"); + }); + }); +}); From 716943b68d9260bf3b9f661c3630f9d32b13f9cf Mon Sep 17 00:00:00 2001 From: Dave Roberts Date: Tue, 12 May 2026 09:53:28 +0100 Subject: [PATCH 3/5] Fix bug in logging --- src/frontend/js/lib/logging.js | 21 ++--- src/frontend/js/lib/logging.test.js | 94 ------------------- .../lib/MessageUploader.test.ts | 23 ----- .../scriptErrorHandler/lib/MessageUploader.ts | 14 ++- 4 files changed, 18 insertions(+), 134 deletions(-) delete mode 100644 src/frontend/js/lib/logging.test.js delete mode 100644 src/frontend/js/lib/util/scriptErrorHandler/lib/MessageUploader.test.ts diff --git a/src/frontend/js/lib/logging.js b/src/frontend/js/lib/logging.js index 135388ceb..fe7a686f5 100644 --- a/src/frontend/js/lib/logging.js +++ b/src/frontend/js/lib/logging.js @@ -1,22 +1,15 @@ -import { MessageUploader } from "util/scriptErrorHandler/lib/MessageUploader"; -import { Uploader } from "util/upload/UploadControl"; +import { uploadMessage } from "util/scriptErrorHandler/lib/MessageUploader"; -// Exported for testing -export class Logging { +class Logging { /** * Create a new Logging instance. - * @param {boolean} overrideAllowLogging True to force allowing of logging - used for testing */ - constructor(overrideAllowLogging = false, uploader = new MessageUploader(new Uploader('/api/script_error', 'POST'))) { - this.uploader = uploader; + constructor() { this.allowLogging = window.test || location.hostname === 'localhost' || location.hostname === '127.0.0.1' || location.hostname.endsWith('.peek.digitpaint.nl'); - if(overrideAllowLogging !== undefined) { - this.allowLogging = overrideAllowLogging; - } } log(...message) { @@ -24,7 +17,7 @@ export class Logging { console.log(...message) } else { const msg = this.formatMessage('log', ...message) - this.uploader(msg) + uploadMessage(msg) } } @@ -33,7 +26,7 @@ export class Logging { console.info(...message) } else { const msg = this.formatMessage('info', ...message) - this.uploader(msg) + uploadMessage(msg) } } @@ -42,7 +35,7 @@ export class Logging { console.warn(...message) } else { const msg = this.formatMessage('warn', ...message) - this.uploader(msg) + uploadMessage(msg) } } @@ -51,7 +44,7 @@ export class Logging { console.error(...message) } else { const msg = this.formatMessage('error', ...message) - this.uploader(msg) + uploadMessage(msg) } } diff --git a/src/frontend/js/lib/logging.test.js b/src/frontend/js/lib/logging.test.js deleted file mode 100644 index 2bfaeeea5..000000000 --- a/src/frontend/js/lib/logging.test.js +++ /dev/null @@ -1,94 +0,0 @@ -import { describe, it, expect } from "@jest/globals"; -import { Logging } from "./logging"; - -describe("Logging", () => { - describe("Local logging", () => { - it("should log a message at log level", () => { - const logger = new Logging(true); - const consoleFn = jest.fn(); - console.log = consoleFn; - - logger.log("This is a log message"); - expect(consoleFn).toHaveBeenCalledWith("This is a log message"); - }); - - it("should log a message at info level", () => { - const logger = new Logging(true); - const consoleFn = jest.fn(); - console.info = consoleFn; - - logger.info("This is an info message"); - expect(consoleFn).toHaveBeenCalledWith("This is an info message"); - }); - - it("should log a message at warn level", () => { - const logger = new Logging(true); - const consoleFn = jest.fn(); - console.warn = consoleFn; - - logger.warn("This is a warning message"); - expect(consoleFn).toHaveBeenCalledWith("This is a warning message"); - }); - - it("should log a message at error level", () => { - const logger = new Logging(true); - const consoleFn = jest.fn(); - console.error = consoleFn; - - logger.error("This is an error message"); - expect(consoleFn).toHaveBeenCalledWith("This is an error message"); - }); - }); - - describe("Remote logging", () => { - it("should send a log message to the server", () => { - const uploadMessage = jest.fn(() => - Promise.resolve({ - ok: true, - json: () => Promise.resolve({ success: true }), - }) - ); - const logger = new Logging(false, uploadMessage); - - logger.log("This is a log message"); - expect(uploadMessage).toHaveBeenCalledWith("log: This is a log message"); - }); - - it("should send an info message to the server", () => { - const uploadMessage = jest.fn(() => - Promise.resolve({ - ok: true, - json: () => Promise.resolve({ success: true }), - }) - ); - const logger = new Logging(false, uploadMessage); - - logger.info("This is an info message"); - expect(uploadMessage).toHaveBeenCalledWith("info: This is an info message"); - }); - - it("should send a warning message to the server", () => { - const uploadMessage = jest.fn(() => - Promise.resolve({ - ok: true, - json: () => Promise.resolve({ success: true }), - }) - ); - const logger = new Logging(false, uploadMessage); - logger.warn("This is a warning message"); - expect(uploadMessage).toHaveBeenCalledWith("warn: This is a warning message"); - }); - - it("should send an error message to the server", () => { - const uploadMessage = jest.fn(() => - Promise.resolve({ - ok: true, - json: () => Promise.resolve({ success: true }), - }) - ); - const logger = new Logging(false, uploadMessage); - logger.error("This is an error message"); - expect(uploadMessage).toHaveBeenCalledWith("error: This is an error message"); - }); - }); -}); diff --git a/src/frontend/js/lib/util/scriptErrorHandler/lib/MessageUploader.test.ts b/src/frontend/js/lib/util/scriptErrorHandler/lib/MessageUploader.test.ts deleted file mode 100644 index b7725c100..000000000 --- a/src/frontend/js/lib/util/scriptErrorHandler/lib/MessageUploader.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { describe, it, expect, jest } from '@jest/globals'; -import { MessageUploader } from './MessageUploader'; - -describe('MessageUploader', () => { - class MockUploader { - upload = jest.fn(); - } - - it('should upload messages correctly', async () => { - const uploader = new MockUploader(); - const messageUploader = new MessageUploader(uploader); - - const messages = { id: 1, content: 'Test message 1' }; - - await messageUploader.uploadMessage(JSON.stringify(messages)); - - expect(uploader.upload).toHaveBeenCalledTimes(1); - expect(uploader.upload).toHaveBeenCalledWith({ - description: JSON.stringify(messages), - url: 'http://localhost/' - }); - }); -}); diff --git a/src/frontend/js/lib/util/scriptErrorHandler/lib/MessageUploader.ts b/src/frontend/js/lib/util/scriptErrorHandler/lib/MessageUploader.ts index b362cfeb7..a89539db0 100644 --- a/src/frontend/js/lib/util/scriptErrorHandler/lib/MessageUploader.ts +++ b/src/frontend/js/lib/util/scriptErrorHandler/lib/MessageUploader.ts @@ -5,15 +5,23 @@ export const uploadMessage = async (message: string) => { description: message, url: window.location.href }; - const uploader = new Uploader('/api/script_error', 'POST'); - const messageUploader = new MessageUploader(uploader); + const messageUploader = MessageUploader.instance; return await messageUploader.uploadMessage(body.description); }; -export class MessageUploader { +class MessageUploader { + private static _instance: MessageUploader; + constructor(private uploader: Uploader) { } + static get instance(): MessageUploader { + if (!this._instance) { + this._instance = new MessageUploader(new Uploader('/api/script_error', 'POST')); + } + return this._instance; + } + async uploadMessage(description: string): Promise { const csrf_token = document.body.dataset.csrf; const body = { From 8475afce0fcb0e56a2d97fe591d469dc6578955e Mon Sep 17 00:00:00 2001 From: Dave Roberts Date: Tue, 12 May 2026 09:54:30 +0100 Subject: [PATCH 4/5] fix bug in logging --- .../button/lib/create-report-button.test.ts | 2 ++ src/frontend/components/button/lib/rename-button.ts | 13 +++++++------ .../components/form-group/autosave/lib/component.js | 1 + .../form-group/input/lib/documentComponent.ts | 3 +++ 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/frontend/components/button/lib/create-report-button.test.ts b/src/frontend/components/button/lib/create-report-button.test.ts index 88f9fc910..03b262e0f 100644 --- a/src/frontend/components/button/lib/create-report-button.test.ts +++ b/src/frontend/components/button/lib/create-report-button.test.ts @@ -1,6 +1,8 @@ +/* eslint-disable */ import "../../../testing/globals.definitions"; import {validateRequiredFields} from 'validation'; import CreateReportButtonComponent from "./create-report-button"; +import { describe, it, expect, jest } from '@jest/globals'; describe('create-report-button', () => { it('does not submit form if no checkboxes are checked', () => { diff --git a/src/frontend/components/button/lib/rename-button.ts b/src/frontend/components/button/lib/rename-button.ts index 4e66e6f31..9db4f1110 100644 --- a/src/frontend/components/button/lib/rename-button.ts +++ b/src/frontend/components/button/lib/rename-button.ts @@ -39,13 +39,14 @@ declare global { */ class RenameButton { private readonly dataClass = 'rename-button'; - private value: string; + private value!: string; /** * Attach event to button * @param {HTMLButtonElement} button Button to attach the event to */ constructor(button: HTMLButtonElement) { + if(!button) return; const $button = $(button); if ($button.data(this.dataClass) === 'true') return; const data = $button.data('fieldId'); @@ -126,8 +127,8 @@ class RenameButton { $(`#rename-confirm-${id}`) .removeClass('hidden') .attr('aria-hidden', null) - .on('click', (e) => { - this.triggerRename(id, ev.target, e) + .on('click', () => { + this.triggerRename(id, ev.target) }); $(`#rename-cancel-${id}`) .removeClass('hidden') @@ -158,7 +159,7 @@ class RenameButton { * @param {JQuery} button The button that was clicked * @param {JQuery.BlurEvent} e The blur event */ - private triggerRename(id: number, button: JQuery, e: JQuery.Event) { + private triggerRename(id: number, button: JQuery) { const previousValue = $(`#current-${id}`).text(); const extension = '.' + previousValue.split('.').pop(); const newName = this.value.endsWith(extension) ? this.value : this.value + extension; @@ -190,8 +191,8 @@ class RenameButton { if(typeof jQuery !== 'undefined') { (function ($) { $.fn.renameButton = function () { - return this.each(function (_: unknown, el: HTMLButtonElement) { - new RenameButton(el); + return this.each((_, el) => { + new RenameButton(el as HTMLButtonElement); }); }; })(jQuery); diff --git a/src/frontend/components/form-group/autosave/lib/component.js b/src/frontend/components/form-group/autosave/lib/component.js index ae8a114a0..9906c0b6e 100644 --- a/src/frontend/components/form-group/autosave/lib/component.js +++ b/src/frontend/components/form-group/autosave/lib/component.js @@ -11,6 +11,7 @@ class AutosaveComponent extends AutosaveBase { */ async initAutosave() { const $field = $(this.element); + /* eslint-disable-next-line @typescript-eslint/no-this-alias */ const self = this; if ($field.data('is-readonly')) return; diff --git a/src/frontend/components/form-group/input/lib/documentComponent.ts b/src/frontend/components/form-group/input/lib/documentComponent.ts index 7c47c4bee..3bd5b306d 100644 --- a/src/frontend/components/form-group/input/lib/documentComponent.ts +++ b/src/frontend/components/form-group/input/lib/documentComponent.ts @@ -70,6 +70,7 @@ class DocumentComponent { if(JSON.parse(e as string)?.message) e = JSON.parse(e as string).message; this.handler.addError(e); + logging.error("Failed to upload file:", e); }); }); } @@ -126,6 +127,7 @@ class DocumentComponent { .hide(); }); } catch (e) { + logging.error("Failed to upload file:", e); this.showException(e instanceof Error || "message" in e ? e.message : e as string ?? e.toString()); } } @@ -191,6 +193,7 @@ class DocumentComponent { this.showException(e); const current = $(`#current-${fileId}`); current.text(oldName); + logging.error("Failed to rename file:", e); } } From a3cd6f55beeb7960638f0608ae46bcf3d73d006e Mon Sep 17 00:00:00 2001 From: Dave Roberts Date: Wed, 13 May 2026 15:38:27 +0100 Subject: [PATCH 5/5] Fix for incorrect import --- src/frontend/js/lib/logging.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/js/lib/logging.js b/src/frontend/js/lib/logging.js index fe7a686f5..da8faa25f 100644 --- a/src/frontend/js/lib/logging.js +++ b/src/frontend/js/lib/logging.js @@ -1,4 +1,4 @@ -import { uploadMessage } from "util/scriptErrorHandler/lib/MessageUploader"; +import { uploadMessage } from "util/scriptErrorHandler"; class Logging { /**