From d43b853ea2ef77a37bb9ed8a12252290433382fb Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Thu, 3 Sep 2026 11:40:27 +0200 Subject: [PATCH 1/4] Add copy URL action to command logs Register the InfoLogger model with `StatefulComponent` so shared stateful UI components can render correctly. Add a `CopyToClipboardComponent` button in the command logs toolbar that copies the current filter query string as a URL. --- InfoLogger/public/index.js | 3 ++- InfoLogger/public/log/commandLogs.js | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/InfoLogger/public/index.js b/InfoLogger/public/index.js index 7b689a2b81..ec64790e86 100644 --- a/InfoLogger/public/index.js +++ b/InfoLogger/public/index.js @@ -19,13 +19,14 @@ sessionService.loadAndHideParameters(); window.sessionService = sessionService; // Import MVC -import { mount } from '/js/src/index.js'; +import { mount, StatefulComponent } from '/js/src/index.js'; import view from './view.js'; import Model from './Model.js'; // Start application const model = new Model(); const debug = true; // shows when redraw is done +StatefulComponent.useRenderer(model); // Register the model for the stateful components mount(document.body, view, model, debug); // Expose model to interact with it the browser's console diff --git a/InfoLogger/public/log/commandLogs.js b/InfoLogger/public/log/commandLogs.js index 06ca8932f7..3df04cd0d7 100644 --- a/InfoLogger/public/log/commandLogs.js +++ b/InfoLogger/public/log/commandLogs.js @@ -20,6 +20,7 @@ import { h, iconMagnifyingGlass, iconPlus, iconMinus, + CopyToClipboardComponent, } from '/js/src/index.js'; import { BUTTON } from '../constants/button-states.const.js'; import { MODE } from '../constants/mode.const.js'; @@ -67,8 +68,22 @@ export const commandLogs = (model) => [ ]), h('', downloadButtonGroup(model.log)), h('', zoomButtonGroup(model.zoom)), + copyButtonOption(model.log.filter), + ]; +/** + * A button component that lets the user copy the url + * + * @param {Model} filterModel - filter model of the application + * @returns {Component} the copy button component + */ +const copyButtonOption = (filterModel) => h( + CopyToClipboardComponent, + { value: filterModel.queryString, id: 'url', className: 'button.btn', style: { minWidth: '100px' } }, + 'Copy URL', +); + /** * Group of buttons for switching between Query and Live modes. * @param {Model} model - root model of the application From 8c91fe40bf95499c657ac41faebd602777bbfbfb Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Thu, 3 Sep 2026 12:11:06 +0200 Subject: [PATCH 2/4] Fix copy url value Switch the command logs copy action to use `location.href`. --- InfoLogger/public/log/commandLogs.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/InfoLogger/public/log/commandLogs.js b/InfoLogger/public/log/commandLogs.js index 3df04cd0d7..b5a4f66098 100644 --- a/InfoLogger/public/log/commandLogs.js +++ b/InfoLogger/public/log/commandLogs.js @@ -68,19 +68,18 @@ export const commandLogs = (model) => [ ]), h('', downloadButtonGroup(model.log)), h('', zoomButtonGroup(model.zoom)), - copyButtonOption(model.log.filter), + copyButtonOption(), ]; /** * A button component that lets the user copy the url * - * @param {Model} filterModel - filter model of the application * @returns {Component} the copy button component */ -const copyButtonOption = (filterModel) => h( +const copyButtonOption = () => h( CopyToClipboardComponent, - { value: filterModel.queryString, id: 'url', className: 'button.btn', style: { minWidth: '100px' } }, + { value: location.href, id: 'url', className: 'button.btn', style: { minWidth: '100px' } }, 'Copy URL', ); From 717ebca7772146a8e0d9aa104460868a92ebbf7d Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Mon, 7 Sep 2026 16:33:14 +0200 Subject: [PATCH 3/4] Should copy the non-debounced version of URL --- InfoLogger/public/log/commandLogs.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/InfoLogger/public/log/commandLogs.js b/InfoLogger/public/log/commandLogs.js index b5a4f66098..21b8f67028 100644 --- a/InfoLogger/public/log/commandLogs.js +++ b/InfoLogger/public/log/commandLogs.js @@ -68,18 +68,25 @@ export const commandLogs = (model) => [ ]), h('', downloadButtonGroup(model.log)), h('', zoomButtonGroup(model.zoom)), - copyButtonOption(), + copyButtonOption(model.log.filter.queryString), ]; /** * A button component that lets the user copy the url * + * @param {string} queryString - the query string to be appended to the URL * @returns {Component} the copy button component */ -const copyButtonOption = () => h( +const copyButtonOption = (queryString) => h( CopyToClipboardComponent, - { value: location.href, id: 'url', className: 'button.btn', style: { minWidth: '100px' } }, + { + // Copy the non-debounced URL with the current query string + value: `${location.origin}${location.pathname}${queryString}`, + id: 'url', + className: 'button.btn', + style: { minWidth: '100px' }, + }, 'Copy URL', ); From 357df2c9b2c13abf52b1164cd59f996aec76c0b5 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Mon, 7 Sep 2026 16:34:16 +0200 Subject: [PATCH 4/4] Add tests for copy URL button Add a new test suite for the copy URL button. The new tests verify the button label and confirm that clicking it copies a URL containing the encoded active filter query. --- InfoLogger/test/mocha-index.js | 1 + InfoLogger/test/public/copy-url-btn-mocha.js | 49 ++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 InfoLogger/test/public/copy-url-btn-mocha.js diff --git a/InfoLogger/test/mocha-index.js b/InfoLogger/test/mocha-index.js index b8e50c7373..c94df8820d 100644 --- a/InfoLogger/test/mocha-index.js +++ b/InfoLogger/test/mocha-index.js @@ -115,6 +115,7 @@ describe('InfoLogger', function () { require('./public/status-bar-mocha'); require('./public/zoom.mocha'); require('./public/log-context-menu-mocha'); + require('./public/copy-url-btn-mocha'); after(async () => { await browser.close(); diff --git a/InfoLogger/test/public/copy-url-btn-mocha.js b/InfoLogger/test/public/copy-url-btn-mocha.js new file mode 100644 index 0000000000..14bc5de832 --- /dev/null +++ b/InfoLogger/test/public/copy-url-btn-mocha.js @@ -0,0 +1,49 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +const assert = require('assert'); +const test = require('../mocha-index'); + +describe('Copy URL button test-suite', async () => { + let baseUrl = null; + let page = null; + + before(async () => { + ({ helpers: { baseUrl }, page } = test); + await page.browser().defaultBrowserContext().setPermission( + new URL(baseUrl).origin, + { permission: { name: 'clipboard-read' }, state: 'granted' }, + { permission: { name: 'clipboard-write' }, state: 'granted' }, + ); + await page.goto(baseUrl, { waitUntil: 'networkidle0' }); + }); + + it('should display the button with the correct label', async () => { + const button = await page.$('#copy-url'); + const label = await page.evaluate((el) => el.textContent, button); + assert.strictEqual(label, 'Copy URL'); + }); + + it('should copy a URL carrying the active filter', async () => { + await page.evaluate(() => { + window.model.log.filter.setCriteria('message', 'match', 'needle'); + window.model.notify(); + }); + await page.click('#copy-url'); + const copiedText = await page.evaluate(() => navigator.clipboard.readText()); + const expectedUrl = `${baseUrl}?q=%7B%22message%22%3A%7B%22match%22` + + '%3A%22needle%22%7D%2C%22severity%22%3A%7B%22in%22%3A%22I%20W%20E%20F%22%7D%7D'; + assert.strictEqual(copiedText, expectedUrl); + }); +});