Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5e34fb4
Add copy URL action to command logs
isaachilly Sep 3, 2026
9f72d95
Fix copy url value
isaachilly Sep 3, 2026
cd2c2f3
Should copy the non-debounced version of URL
isaachilly Sep 7, 2026
93477f4
Add tests for copy URL button
isaachilly Sep 7, 2026
1612b9a
Rename copy button
isaachilly Sep 8, 2026
5c8bb86
Remove references to location in a view
isaachilly Sep 8, 2026
193ab8f
Change copyURL button to reference correct attr
isaachilly Sep 8, 2026
7d8ac6a
Reset clipboard test permissions and improve JSDOC
isaachilly Sep 8, 2026
46b9a01
Remove window location calls from log filter model
isaachilly Sep 8, 2026
07d64fe
Share render wait helper across UI tests
isaachilly Sep 8, 2026
43831c5
Fix URL copy button prop name
isaachilly Sep 8, 2026
9bc4e94
Notify on Copy URL clipboard failures
isaachilly Sep 8, 2026
7b3694b
Stabilize copy URL failure notification test
isaachilly Sep 8, 2026
6a8a942
Trying to stabilise notifcation test
isaachilly Sep 8, 2026
ff585d9
Fix unreliable test
isaachilly Sep 8, 2026
ccfd9da
Use another way to mock erroneous clipboard
isaachilly Sep 8, 2026
0b1e4bf
Fix wrong assert text
isaachilly Sep 8, 2026
b99ab29
Stabilise test again
isaachilly Sep 8, 2026
3ae71d4
Use global model in copy URL mocha test
isaachilly Sep 8, 2026
d64ac9f
Update copy URL button test
isaachilly Sep 11, 2026
267b05a
Document better the shareable URL getter
isaachilly Sep 11, 2026
209b2cb
Improve the URL notification callback
isaachilly Sep 11, 2026
5912441
Fix JSDoc
isaachilly Sep 11, 2026
14a9375
Make comment better
isaachilly Sep 16, 2026
4c0de16
Match parameter of onFailure
isaachilly Sep 16, 2026
d99a0a3
Center copy URL button text
isaachilly Sep 16, 2026
79688c7
Harden against querystring being null and resulting in a lonely ?
isaachilly Sep 16, 2026
30f3fd8
Revert 4c0de166
isaachilly Sep 16, 2026
6a483f9
Revert 79688c76
isaachilly Sep 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions InfoLogger/public/Model.js
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,17 @@ export default class Model extends Observable {
this.router.go(this.log.filter.queryString, true, true);
}

/**
* Get the shareable URL with the current filter query string
* Built from the model rather than the address bar, which is debounced.
* @returns {string} - the shareable URL
*/
get shareableURL() {
const url = this.router.getUrl();
url.search = this.log.filter.queryString;
return url.href;
}

/**
* Toggle inspector on the right
*/
Expand Down
3 changes: 2 additions & 1 deletion InfoLogger/public/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions InfoLogger/public/log/commandLogs.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -67,8 +68,32 @@ export const commandLogs = (model) => [
]),
h('', downloadButtonGroup(model.log)),
h('', zoomButtonGroup(model.zoom)),
copyURLButton(
model.shareableURL,
(message, type, duration) => model.notification.show(message, type, duration),
),
];

/**
* A button component that lets the user copy the url
* @param {string} url - the URL to be copied to the clipboard
* @param {(message: string, type: string, duration: number) => void} showNotification -
* function to show notification to the user
* @returns {Component} the copy button component
*/
const copyURLButton = (url, showNotification) => h(
CopyToClipboardComponent,
{
value: url,
id: 'url',
className: '',
style: { minWidth: '100px' },
contentClassName: 'justify-center',
onFailure: ({ message }) => showNotification(`Could not copy URL: ${message}`, 'danger', 3000),
},
'Copy URL',
);

/**
* Group of buttons for switching between Query and Live modes.
* @param {Model} model - root model of the application
Expand Down
1 change: 1 addition & 0 deletions InfoLogger/test/mocha-index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
12 changes: 2 additions & 10 deletions InfoLogger/test/public/context-menu-test-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,14 @@
* or submit itself to any jurisdiction.
*/

const { waitForNextRender } = require('../utils/utils.js');

const isContextMenuOpen = async (page) => await page.evaluate(() => window.model.log.contextMenu.isOpen);

const getMenuActionLabels = async (page) => page.evaluate(() =>
Array.from(document.querySelectorAll('.cell-context-menu-item .ph2.w-100'))
.map((el) => el.textContent.trim()));

/*
* A stale menu from a previous test can already satisfy a waitForSelector check
* before the pending redraw (reflecting the new state) has actually run.
* Waiting for two animation frames guarantees the debounced redraw has fired
* at least once since the mutation.
*/
const waitForNextRender = (page) => page.evaluate(() => new Promise((resolve) => {
requestAnimationFrame(() => requestAnimationFrame(resolve));
}));

const openContextMenu = async (page, field, value, x, y) => {
await page.evaluate((field, value, x, y) => {
window.model.log.contextMenu.show(field, value, x, y);
Expand Down
73 changes: 73 additions & 0 deletions InfoLogger/test/public/copy-url-btn-mocha.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* @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');

const { waitForNextRender } = require('../utils/utils.js');

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' });
});

after(async () => {
await page.browser().defaultBrowserContext().clearPermissionOverrides();
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(() => {
model.log.setCriteria('message', 'match', 'needle');
model.notify();
});
await waitForNextRender(page);
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);
});

it('should display a notification on copy failure', async () => {
await page.evaluate(() => {
model.notification.hide();
Object.defineProperty(navigator, 'clipboard', {
value: {
writeText: () => Promise.reject(new Error('Simulated copy failure')),
},
configurable: true,
});
});

await page.click('#copy-url');

await page.waitForSelector('.notification-content.bg-danger.notification-open');
});
});
11 changes: 11 additions & 0 deletions InfoLogger/test/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,18 @@ async function waitForTextInElement(page, selector, text) {
);
}

/*
* A stale element from a previous test can already satisfy a waitForSelector check
* before the pending redraw (reflecting the new state) has actually run.
* Waiting for two animation frames guarantees the redraw has fired at least once
* since the mutation.
*/
const waitForNextRender = (page) => page.evaluate(() => new Promise((resolve) => {
requestAnimationFrame(() => requestAnimationFrame(resolve));
}));

module.exports = {
injectLogs,
waitForTextInElement,
waitForNextRender,
};
Loading