Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
61dc057
Add copy URL action to command logs
isaachilly Sep 3, 2026
f35807c
Fix copy url value
isaachilly Sep 3, 2026
9893f94
Should copy the non-debounced version of URL
isaachilly Sep 7, 2026
31c1beb
Add tests for copy URL button
isaachilly Sep 7, 2026
93b3a28
Rename copy button
isaachilly Sep 8, 2026
c919cf3
Remove references to location in a view
isaachilly Sep 8, 2026
b6f200c
Change copyURL button to reference correct attr
isaachilly Sep 8, 2026
216f5c3
Reset clipboard test permissions and improve JSDOC
isaachilly Sep 8, 2026
ab89245
Remove window location calls from log filter model
isaachilly Sep 8, 2026
47c4b30
Share render wait helper across UI tests
isaachilly Sep 8, 2026
f7e1600
Fix URL copy button prop name
isaachilly Sep 8, 2026
01a4922
Notify on Copy URL clipboard failures
isaachilly Sep 8, 2026
047b441
Stabilize copy URL failure notification test
isaachilly Sep 8, 2026
4117f4f
Trying to stabilise notifcation test
isaachilly Sep 8, 2026
1b9b616
Fix unreliable test
isaachilly Sep 8, 2026
f6a41dd
Use another way to mock erroneous clipboard
isaachilly Sep 8, 2026
5077114
Fix wrong assert text
isaachilly Sep 8, 2026
d61abd9
Stabilise test again
isaachilly Sep 8, 2026
2964ce4
Use global model in copy URL mocha test
isaachilly Sep 8, 2026
3178b9c
Update copy URL button test
isaachilly Sep 11, 2026
f6302da
Document better the shareable URL getter
isaachilly Sep 11, 2026
199be7d
Improve the URL notification callback
isaachilly Sep 11, 2026
63770cf
Fix JSDoc
isaachilly Sep 11, 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 only updates on a 500 ms rate limit.
* @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
24 changes: 24 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,31 @@ 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' },
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