From a7f64f865fb734d93947f24a4345b2d9228c2ff9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Nov 2024 13:26:47 +0100 Subject: [PATCH 1/5] Bump the dev-dependencies group with 2 updates (#1794) Bumps the dev-dependencies group with 2 updates: [mocha](https://github.com/mochajs/mocha) and [puppeteer](https://github.com/puppeteer/puppeteer). Updates `mocha` from 10.7.0 to 10.8.2 - [Release notes](https://github.com/mochajs/mocha/releases) - [Changelog](https://github.com/mochajs/mocha/blob/main/CHANGELOG.md) - [Commits](https://github.com/mochajs/mocha/compare/v10.7.0...v10.8.2) Updates `puppeteer` from 23.6.1 to 23.7.0 - [Release notes](https://github.com/puppeteer/puppeteer/releases) - [Changelog](https://github.com/puppeteer/puppeteer/blob/main/release-please-config.json) - [Commits](https://github.com/puppeteer/puppeteer/compare/puppeteer-v23.6.1...puppeteer-v23.7.0) --- updated-dependencies: - dependency-name: mocha dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies - dependency-name: puppeteer dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Martin Boulais <31805063+martinboulais@users.noreply.github.com> --- package-lock.json | 42 +++++++++++++++++++++--------------------- package.json | 4 ++-- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2685e25836..e147615469 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,10 +47,10 @@ "date-and-time": "3.6.0", "eslint": "8.57.0", "js-yaml": "4.1.0", - "mocha": "10.7.0", + "mocha": "10.8.2", "nodemon": "3.1.3", "nyc": "17.1.0", - "puppeteer": "23.6.1", + "puppeteer": "23.7.0", "puppeteer-to-istanbul": "1.4.0", "sequelize-cli": "6.6.0", "sinon": "19.0.2", @@ -1109,12 +1109,12 @@ "inBundle": true }, "node_modules/@puppeteer/browsers": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.4.0.tgz", - "integrity": "sha512-x8J1csfIygOwf6D6qUAZ0ASk3z63zPb7wkNeHRerCMh82qWKUrOgkuP005AJC8lDL6/evtXETGEJVcwykKT4/g==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.4.1.tgz", + "integrity": "sha512-0kdAbmic3J09I6dT8e9vE2JOCSt13wHCW5x/ly8TSt2bDtuIWe2TgLZZDHdcziw9AVCzflMAXCrVyRIhIs44Ng==", "dev": true, "dependencies": { - "debug": "^4.3.6", + "debug": "^4.3.7", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.4.0", @@ -5798,9 +5798,9 @@ } }, "node_modules/mocha": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.0.tgz", - "integrity": "sha512-v8/rBWr2VO5YkspYINnvu81inSz2y3ODJrhO175/Exzor1RcEZZkizgE2A+w/CAXXoESS8Kys5E62dOHGHzULA==", + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", "dev": true, "dependencies": { "ansi-colors": "^4.1.3", @@ -6959,17 +6959,17 @@ } }, "node_modules/puppeteer": { - "version": "23.6.1", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-23.6.1.tgz", - "integrity": "sha512-8+ALGQgwXd3P/tGcuSsxTPGDaOQIjcDIm04I5hpWZv/PiN5q8bQNHRUyfYrifT+flnM9aTWCP7tLEzuB6SlIgA==", + "version": "23.7.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-23.7.0.tgz", + "integrity": "sha512-YTgo0KFe8NtBcI9hCu/xsjPFumEhu8kA7QqLr6Uh79JcEsUcUt+go966NgKYXJ+P3Fuefrzn2SXwV3cyOe/UcQ==", "dev": true, "hasInstallScript": true, "dependencies": { - "@puppeteer/browsers": "2.4.0", + "@puppeteer/browsers": "2.4.1", "chromium-bidi": "0.8.0", "cosmiconfig": "^9.0.0", "devtools-protocol": "0.0.1354347", - "puppeteer-core": "23.6.1", + "puppeteer-core": "23.7.0", "typed-query-selector": "^2.12.0" }, "bin": { @@ -6980,12 +6980,12 @@ } }, "node_modules/puppeteer-core": { - "version": "23.6.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.6.1.tgz", - "integrity": "sha512-DoNLAzQfGklPauEn33N4h9cM9GubJSINEn+AUMwAXwW159Y9JLk5y34Jsbv4c7kG8P0puOYWV9leu2siMZ/QpQ==", + "version": "23.7.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.7.0.tgz", + "integrity": "sha512-0kC81k3K6n6Upg/k04xv+Mi8yy62bNAJiK7LCA71zfq2XKEo9WAzas1t6UQiLgaNHtGNKM0d1KbR56p/+mgEiQ==", "dev": true, "dependencies": { - "@puppeteer/browsers": "2.4.0", + "@puppeteer/browsers": "2.4.1", "chromium-bidi": "0.8.0", "debug": "^4.3.7", "devtools-protocol": "0.0.1354347", @@ -8244,9 +8244,9 @@ } }, "node_modules/tslib": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", - "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true }, "node_modules/type": { diff --git a/package.json b/package.json index cb822b10ea..1c2dbd9345 100644 --- a/package.json +++ b/package.json @@ -50,10 +50,10 @@ "date-and-time": "3.6.0", "eslint": "8.57.0", "js-yaml": "4.1.0", - "mocha": "10.7.0", + "mocha": "10.8.2", "nodemon": "3.1.3", "nyc": "17.1.0", - "puppeteer": "23.6.1", + "puppeteer": "23.7.0", "puppeteer-to-istanbul": "1.4.0", "sequelize-cli": "6.6.0", "sinon": "19.0.2", From c86722efb2d6e92351025d60522bb2e6702beb40 Mon Sep 17 00:00:00 2001 From: Martin Boulais <31805063+martinboulais@users.noreply.github.com> Date: Sun, 10 Nov 2024 08:40:05 +0100 Subject: [PATCH 2/5] [O2B-1375] Minor impromvements to environment configuration display (#1797) --- .../Details/EnvironmentDetailsPage.js | 5 ++++- test/public/envs/detailsPage.test.js | 17 ++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/public/views/Environments/Details/EnvironmentDetailsPage.js b/lib/public/views/Environments/Details/EnvironmentDetailsPage.js index f97ace3117..74aabc1f1e 100644 --- a/lib/public/views/Environments/Details/EnvironmentDetailsPage.js +++ b/lib/public/views/Environments/Details/EnvironmentDetailsPage.js @@ -36,7 +36,9 @@ const configurationDisplay = (rawConfiguration) => { try { const configuration = JSON.parse(rawConfiguration); return table( - Object.entries(configuration).map(([key, value]) => ({ key, value })), + Object.entries(configuration) + .map(([key, value]) => ({ key, value })) + .sort(({ key: keyA }, { key: keyB }) => keyA.localeCompare(keyB)), { key: { name: 'Key', @@ -44,6 +46,7 @@ const configurationDisplay = (rawConfiguration) => { }, value: { name: 'Value', + noEllipsis: true, visible: true, format: (value) => JSON.stringify(value), }, diff --git a/test/public/envs/detailsPage.test.js b/test/public/envs/detailsPage.test.js index 4241d06e3e..1734c8cff6 100644 --- a/test/public/envs/detailsPage.test.js +++ b/test/public/envs/detailsPage.test.js @@ -21,6 +21,7 @@ const { pressElement, waitForNavigation, expectLink, + waitForTableLength, } = require('../defaults.js'); const { resetDatabaseContent } = require('../../utilities/resetDatabaseContent.js'); @@ -33,7 +34,7 @@ module.exports = () => { before(async () => { [page, browser] = await defaultBefore(page, browser); await page.setViewport({ - width: 1920, + width: 800, height: 1080, deviceScaleFactor: 1, }); @@ -133,6 +134,16 @@ module.exports = () => { '#raw-configuration-pane table #rowdcs_enabled-value-text', 'false', ); + + await waitForNavigation(page, () => pressElement(page, '#env-overview')); + await waitForNavigation(page, () => pressElement(page, '#rowCmCvjNbg a:first-of-type')); + await pressElement(page, '#raw-configuration-tab'); + + await waitForTableLength(page, 18); + expect(await page.evaluate(() => { + const cellContents = Array.from(document.querySelectorAll('table tbody tr td:first-child')).map((element) => element.innerText); + return cellContents.every((v, i) => !i || v.localeCompare(cellContents[i - 1]) >= 0); + })).to.be.true; }); it('should successfully display FLP and ECS links', async () => { @@ -140,11 +151,11 @@ module.exports = () => { await expectLink(page, `${containerSelector} a:nth-of-type(1)`, { href: - 'http://localhost:8081/?q={%22partition%22:{%22match%22:%22Dxi029djX%22},%22severity%22:{%22in%22:%22W%20E%20F%22}}', + 'http://localhost:8081/?q={%22partition%22:{%22match%22:%22CmCvjNbg%22},%22severity%22:{%22in%22:%22W%20E%20F%22}}', innerText: 'FLP', }); await expectLink(page, `${containerSelector} a:nth-of-type(2)`, { - href: 'http://localhost:8080/?page=environment&id=Dxi029djX', + href: 'http://localhost:8080/?page=environment&id=CmCvjNbg', innerText: 'ECS', }); }); From 935a09662f113024b73b01f4d1a3a3c5214e4266 Mon Sep 17 00:00:00 2001 From: Martin Boulais <31805063+martinboulais@users.noreply.github.com> Date: Sun, 10 Nov 2024 20:15:51 +0100 Subject: [PATCH 3/5] [O2B-1399] Fix physical constants values (#1796) * [O2B-1399] Fix physical constants values * Clean values and add comments * Fix tests --- .../20231025101611-create-lhc-periods-view.js | 1 + .../20241105125509-fix-physical-constants.js | 39 +++++++++++++++++++ test/api/lhcPeriodsStatistics.test.js | 2 +- .../LhcPeriodStatisticsService.test.js | 2 +- 4 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 lib/database/migrations/20241105125509-fix-physical-constants.js diff --git a/lib/database/migrations/20231025101611-create-lhc-periods-view.js b/lib/database/migrations/20231025101611-create-lhc-periods-view.js index 3aaa980666..c8c98bb472 100644 --- a/lib/database/migrations/20231025101611-create-lhc-periods-view.js +++ b/lib/database/migrations/20231025101611-create-lhc-periods-view.js @@ -1,5 +1,6 @@ 'use strict'; +// Incorrect values, fixed by migration 20241105125509-fix-physical-constants.js const PARTICLES_PROPERTIES = { // 2 * sqrt(ATOMIC_MASS / ATOMIC_NUMBER) ['2#Z/A_pp']: 2 * Math.sqrt(1 / 1), ['2#Z/A_PbPb']: 2 * Math.sqrt(207.2 / 82), diff --git a/lib/database/migrations/20241105125509-fix-physical-constants.js b/lib/database/migrations/20241105125509-fix-physical-constants.js new file mode 100644 index 0000000000..e313645f08 --- /dev/null +++ b/lib/database/migrations/20241105125509-fix-physical-constants.js @@ -0,0 +1,39 @@ +'use strict'; + +// Fixes values set in 20231025101611-create-lhc-periods-view.js + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + up: async (queryInterface) => queryInterface.sequelize.transaction(async (transaction) => { + await queryInterface.sequelize.query( + // 2 * ATOMIC_NUMBER /ATOMIC_MASS => 2 * 82 / 207.2 + 'UPDATE physical_constants SET value=0.791505792 WHERE `key`=\'2#Z/A_PbPb\'', + { transaction }, + ); + await queryInterface.sequelize.query( + // 2 * ATOMIC_NUMBER /ATOMIC_MASS => 2 * 8 / 15.999 + 'UPDATE physical_constants SET value=1.000062504 WHERE `key`=\'2#Z/A_OO\'', + { transaction }, + ); + await queryInterface.sequelize.query( + // 2 * ATOMIC_NUMBER /ATOMIC_MASS => 2 * 54 / 131.293 + 'UPDATE physical_constants SET value=0.822587647 WHERE `key`=\'2#Z/A_XeXe\'', + { transaction }, + ); + }), + + down: async (queryInterface) => queryInterface.sequelize.transaction(async (transaction) => { + await queryInterface.sequelize.query( + 'UPDATE physical_constants SET value=3.1792006972147466 WHERE `key`=\'2#Z/A_PbPb\'', + { transaction }, + ); + await queryInterface.sequelize.query( + 'UPDATE physical_constants SET value=7.999974999960937 WHERE `key`=\'2#Z/A_OO\'', + { transaction }, + ); + await queryInterface.sequelize.query( + 'UPDATE physical_constants SET value=22.916369695045503 WHERE `key`=\'2#Z/A_XeXe\'', + { transaction }, + ); + }), +}; diff --git a/test/api/lhcPeriodsStatistics.test.js b/test/api/lhcPeriodsStatistics.test.js index 11db0b91f3..481cc1bbc5 100644 --- a/test/api/lhcPeriodsStatistics.test.js +++ b/test/api/lhcPeriodsStatistics.test.js @@ -18,7 +18,7 @@ const { resetDatabaseContent } = require('../utilities/resetDatabaseContent.js') const lhcPeriod_LHC22a = { id: 1, - avgCenterOfMassEnergy: 108.64388160203008, + avgCenterOfMassEnergy: 27.04839037960254, lhcPeriod: { id: 1, name: 'LHC22a', diff --git a/test/lib/server/services/lhcPeriod/LhcPeriodStatisticsService.test.js b/test/lib/server/services/lhcPeriod/LhcPeriodStatisticsService.test.js index 861ee8c19c..604f2fba52 100644 --- a/test/lib/server/services/lhcPeriod/LhcPeriodStatisticsService.test.js +++ b/test/lib/server/services/lhcPeriod/LhcPeriodStatisticsService.test.js @@ -19,7 +19,7 @@ const { NotFoundError } = require('../../../../../lib/server/errors/NotFoundErro const lhcPeriod_LHC22a = { id: 1, - avgCenterOfMassEnergy: 108.64388160203008, + avgCenterOfMassEnergy: 27.04839037960254, lhcPeriod: { id: 1, name: 'LHC22a', From ab2006b97ef6009e79006dc12396f81c6b236851 Mon Sep 17 00:00:00 2001 From: xsalonx <65893715+xsalonx@users.noreply.github.com> Date: Sun, 10 Nov 2024 20:54:39 +0100 Subject: [PATCH 4/5] [O2B-1154] QC flags visualization (#1781) * to separate file * refactor * rename * docs * extract proper timestamp from run * cleanup * typedefs * typdefs * split * squash code * hadle incorrect states * add seeder * time range * fix timestamp format * add timestamps to seeder * test WIP * fetching tests ok * WIP * amend test * test passing * amend linter * docs * ver test WIP * ver test OK * add addociation name * add controller * add router * refactor load * amend * fetching works * add getter * docs * add breadcramps * add repo * add repo * get all reasons service function * add test * add ep,; test WIP * add provenenace * revoke * test WIP * t WIP * test * WIP * simplify test * wIP * WIP * change error messages * qcf creation test ok * add coalesc comment to null * expose ep * a * add test ok * typo * typo * cherry pick colouring * del * miss * fixed * revoke * rm verifications * cherry-pick * name archived * obsolate to archived * simplify association * a * Revert "a" This reverts commit a7b880c5b140d096e8c66ff8079800203628a1c6. * Revert "simplify association" This reverts commit ad453da5abd365366090dc7b0ef75496513b2c6e. * docs * rename * simplify * refactor * refactor * refactor * a * add assoc * rename * cleanup * fix associations * comment * rename table * reason to type * rename * rename * docs * cleanup * refactor * rename * fix * WIP * typo * fix * WIP * typo * rename * serv fixed * seeders * fix Test * simp * docs * WIP * add test * rename * fix test * merge WIP * rename * cleanup * rename * seeders and test * cleanup * cleanup * split * test * rename * fix * comment * fix * f * docs * cleaunp * docs * tfix * fix test * add st wip * restyle' * add controller * fix sorting * typo * test * extend sorting * test * test' * fix test * tess wip * fix * test * test ok * test ok * revoke * revoke * revoke * cleanup * use dates * desc order * test * test * rename * simp, rename * refactor * cleanup * fix path * cleanup * docs * move ChartColors back to statistics dir * cleanup * cleanup * cleanup * cleanup * cleanup * adjust visualization * line chart fixed * docs * wip * fix order * fix order * a * keep all records * cleanup * cleanup * patterns work * cleanup * effective periods * mod pattern * refactor patterns * docs * docs * fix * cleanup * fix tests * remove unnecessary tests * clenaup * add test * cleanup; handle errors * make comment more natural * add var * add getter * typo * fix docs * fix docs * extended comment * use for loop * a * cleanup * typo * use index instead of independent * a * docS * rename; cleanup * clenaup * a * rename * cleanup * refactor * docs --------- Co-authored-by: Martin Boulais <31805063+martinboulais@users.noreply.github.com> --- lib/database/adapters/QcFlagAdapter.js | 3 + .../chart/barChart/barChartComponent.js | 12 +- .../chart/lineChart/lineChartComponent.js | 4 +- .../chart/rendering/BarChartRenderer.js | 89 +++++++++++--- .../common/chart/rendering/ChartRenderer.js | 95 +++++++++++---- .../chart/rendering/LineChartRenderer.js | 11 +- .../rendering/dataset/renderDatasetAsBars.js | 91 ++++++++++++--- .../chart/structures/BarPropertiesProvider.js | 28 +++++ .../common/chart/structures/ChartBar.js | 19 +++ .../common/chart/structures/DrawableData.js | 17 +++ .../chart/structures/VisualProperties.js | 19 +++ .../QcFlagsForDataPassOverviewPage.js | 25 +++- .../QcFlags/Overview/QcFlagsOverviewModel.js | 12 ++ .../qcFlagsChartComponent.js | 110 ++++++++++++++++++ .../qualityControlFlag/QcFlagService.js | 2 + test/api/qcFlags.test.js | 1 + .../qualityControlFlag/QcFlagService.test.js | 1 + .../qcFlags/forDataPassOverview.test.js | 50 +------- .../qcFlags/forSimulationPassOverview.test.js | 15 --- 19 files changed, 468 insertions(+), 136 deletions(-) create mode 100644 lib/public/components/common/chart/structures/BarPropertiesProvider.js create mode 100644 lib/public/components/common/chart/structures/ChartBar.js create mode 100644 lib/public/components/common/chart/structures/DrawableData.js create mode 100644 lib/public/components/common/chart/structures/VisualProperties.js create mode 100644 lib/public/views/QcFlags/qcFlagsVisualization/qcFlagsChartComponent.js diff --git a/lib/database/adapters/QcFlagAdapter.js b/lib/database/adapters/QcFlagAdapter.js index 3df98029ea..60b18b0eda 100644 --- a/lib/database/adapters/QcFlagAdapter.js +++ b/lib/database/adapters/QcFlagAdapter.js @@ -23,6 +23,7 @@ class QcFlagAdapter { this.qcFlagTypeAdapter = null; this.qcFlagVerificationAdapter = null; + this.qcFlagEffectivePeriodAdapter = null; this.userAdapter = null; } @@ -48,6 +49,7 @@ class QcFlagAdapter { flagTypeId, flagType, verifications, + effectivePeriods, } = databaseObject; return { @@ -65,6 +67,7 @@ class QcFlagAdapter { createdAt: createdAt ? new Date(createdAt).getTime() : null, updatedAt: createdAt ? new Date(updatedAt).getTime() : null, verifications: (verifications ?? []).map(this.qcFlagVerificationAdapter.toEntity), + effectivePeriods: (effectivePeriods ?? []).map(this.qcFlagEffectivePeriodAdapter.toEntity), }; } } diff --git a/lib/public/components/common/chart/barChart/barChartComponent.js b/lib/public/components/common/chart/barChart/barChartComponent.js index 111c00882f..3b2339ce95 100644 --- a/lib/public/components/common/chart/barChart/barChartComponent.js +++ b/lib/public/components/common/chart/barChart/barChartComponent.js @@ -26,15 +26,15 @@ import { BarChartRenderer } from '../rendering/BarChartRenderer.js'; class BarChartClassComponent { /** * Constructor - * @param {{attrs: {points: Point[], configuration: BarChartComponentConfiguration}}} vnode the component's vnode + * @param {{attrs: {bars: Bar[], configuration: BarChartComponentConfiguration}}} vnode the component's vnode */ constructor({ attrs: { - points, + bars, configuration, }, }) { - this._chartRenderer = new BarChartRenderer(configuration.chartConfiguration, points); + this._chartRenderer = new BarChartRenderer(configuration.chartConfiguration, bars); } /** @@ -69,10 +69,10 @@ class BarChartClassComponent { /** * Render a line chart component * - * @param {Point[]} points the points to display + * @param {ChartBar[]} bars the bars to display * @param {BarChartComponentConfiguration} configuration the configuration of the component * @return {Component} the resulting component */ -export const barChartComponent = (points, configuration) => points.length - ? h(BarChartClassComponent, { points, configuration }) +export const barChartComponent = (bars, configuration) => bars.length + ? h(BarChartClassComponent, { bars, configuration }) : h('.w-100.h-100.flex-row.items-center.justify-center', h('', configuration.placeholder || 'No data')); diff --git a/lib/public/components/common/chart/lineChart/lineChartComponent.js b/lib/public/components/common/chart/lineChart/lineChartComponent.js index 2fd5385b1a..073ddd5c16 100644 --- a/lib/public/components/common/chart/lineChart/lineChartComponent.js +++ b/lib/public/components/common/chart/lineChart/lineChartComponent.js @@ -62,10 +62,10 @@ class LineChartClassComponent { } // eslint-disable-next-line require-jsdoc - onupdate({ attrs: { points, configuration } }) { + onupdate({ attrs: { data, configuration } }) { this.configuration = configuration; - this._chartRenderer = new LineChartRenderer(configuration.chartConfiguration, points); + this._chartRenderer = new LineChartRenderer(configuration.chartConfiguration, data); } // eslint-disable-next-line require-jsdoc diff --git a/lib/public/components/common/chart/rendering/BarChartRenderer.js b/lib/public/components/common/chart/rendering/BarChartRenderer.js index cfb424dc74..04b7b7390e 100644 --- a/lib/public/components/common/chart/rendering/BarChartRenderer.js +++ b/lib/public/components/common/chart/rendering/BarChartRenderer.js @@ -31,14 +31,83 @@ export class BarChartRenderer extends ChartRenderer { * Constructor * * @param {BarChartConfiguration} configuration the chart configuration - * @param {Point[]} points the points to render + * @param {ChartBar[]} bars the bars to render */ - constructor(configuration, points) { - super(configuration, points); + constructor(configuration, bars) { + super(configuration, bars); - if (this._stackedY) { + if (this._stackedValues) { throw new Error('Bar chart not implemented for stacked values'); } + + /** + * Get bar length - size along value axis + * @param {ChartBar} bar bar + * @return {number} bar length + */ + const getBarLength = ({ x, y }) => { + if (this.isXIndexAxis) { + if (Array.isArray(y)) { + throw new Error('Range and stacked bars not implemented for index axis \'x\''); + } + return this._chartDrawingZone.top + this._chartDrawingZone.height - this.yScale(y); + } else if (Array.isArray(x)) { // Index axis is 'y' + if (!this._stackedValues) { + if (x.length === 2) { + return this.xScale(x[1]) - this.xScale(x[0]); + } else { + throw new Error('For range bars you need to provide array of two numbers'); + } + } else { + throw new Error('Bar chart not implemented for stacked values'); + } + } else { + return this.xScale(x); + } + }; + + /** + * Get bars thickness - size along index axis + * @return {number} thickness of bars + */ + const getBarThickness = () => this.indexAxisScale.bandwidth(); + + /** + * @type {ChartBarPropertiesProvider} + */ + this._barPropertiesProvider = { + getX: ({ x }) => { + if (this.isXIndexAxis) { + return this.xScale(x); + } else if (Array.isArray(x)) { // Index axis is 'y' + if (!this._stackedValues) { + if (x.length === 2) { + return this.xScale(x[0]); + } else { + throw new Error('For range bars you need to provide array of two numbers'); + } + } else { + throw new Error('Bar chart not implemented for stacked values'); + } + } else { + return this._chartDrawingZone.left; + } + }, + getY: ({ y }) => { + if (this.isXIndexAxis) { + if (Array.isArray(y)) { + throw new Error('Range and stacked bars not implemented for index axis \'x\''); + } + } + return this.yScale(y); + }, + getWidth: this.isXIndexAxis + ? getBarThickness + : getBarLength, + getHeight: this.isXIndexAxis + ? getBarLength + : getBarThickness, + }; } // eslint-disable-next-line valid-jsdoc @@ -46,21 +115,13 @@ export class BarChartRenderer extends ChartRenderer { * @inheritDoc */ renderDataset(datasetIndex, svg) { - /** @type {PointLocator} */ - const pointLocator = { - getX: ({ x }) => this.xScale(x), - getY: ({ y }) => this.yScale(y), - }; - const { bar: barConfiguration } = this._datasets[datasetIndex] || {}; // Display datasets renderDatasetAsBars( svg, - this._points, - this.xScale.bandwidth(), - this._chartDrawingZone, - pointLocator, + this._data, + this._barPropertiesProvider, barConfiguration, ); } diff --git a/lib/public/components/common/chart/rendering/ChartRenderer.js b/lib/public/components/common/chart/rendering/ChartRenderer.js index 393995dd33..4a57856dcb 100644 --- a/lib/public/components/common/chart/rendering/ChartRenderer.js +++ b/lib/public/components/common/chart/rendering/ChartRenderer.js @@ -42,6 +42,9 @@ const DEFAULT_CHART_MARGIN = { * @return {T} the point's projection */ +export const X_AXIS_NAME = 'x'; +export const Y_AXIS_NAME = 'y'; + /** * Base class for any chart rendering class */ @@ -51,21 +54,34 @@ export class ChartRenderer { * @template T * * @param {ChartConfiguration} configuration the chart's configuration - * @param {(Point[])} points the data to draw. If datasets configuration is an array, each point is expected to have an array as y value, - * one per dataset + * @param {(DrawableData[])} data the data to draw. + * If datasets configuration is an array: + * - and if index axis is 'x' then each point is expected to have an array as y value, one per dataset + * - and if index axis is 'y', 'x' must contain an array in the same manner */ - constructor(configuration, points) { - if (!points.length) { - throw new Error('The points list can not be empty'); + constructor(configuration, data) { + if (!data.length) { + throw new Error('The data list can not be empty'); } + this._data = data; const { chartMargins: configurationChartMargins = {}, forceZero = false, axis = {}, + indexAxis = X_AXIS_NAME, + renderGrid = true, datasets: configurationDatasets = {}, } = configuration; + if (indexAxis !== X_AXIS_NAME && indexAxis !== Y_AXIS_NAME) { + throw new Error(`Index axis can be only '${X_AXIS_NAME}' or '${Y_AXIS_NAME}'` + + `, it cannot be '${this._indexAxis}'`); + } + this._indexAxis = indexAxis; + + this._renderGrid = renderGrid; + // Configuration this._chartMargins = { @@ -76,9 +92,8 @@ export class ChartRenderer { }; this._forceZero = forceZero; - this._stackedY = Array.isArray(configurationDatasets); - - this._datasets = this._stackedY ? configurationDatasets : [configurationDatasets]; + this._stackedValues = Array.isArray(configurationDatasets); + this._datasets = this._stackedValues ? configurationDatasets : [configurationDatasets]; this._axis = axis; @@ -94,27 +109,39 @@ export class ChartRenderer { height: 0, }; - this._points = points; + const valueAxis = this.isXIndexAxis ? Y_AXIS_NAME : X_AXIS_NAME; + const { [valueAxis]: { + min: valueAxisMin, + max: valueAxisMax, + } } = this.getAxisConfiguration(); - const { y: { min: yMin, max: yMax } } = this.getAxisConfiguration(); - this._xAxisScaleFactory = new EnumerableBasedScaleFactory({ point: this.isPoint }); - const yAxisScaleFactoryConfiguration = { forceRange: { min: yMin, max: yMax } }; + const indexAxisScaleFactory = new EnumerableBasedScaleFactory({ point: this.isPoint }); + const valueAxisScaleFactoryConfiguration = { forceRange: { min: valueAxisMin, max: valueAxisMax } }; if (this._forceZero) { - yAxisScaleFactoryConfiguration.minimalRange = { min: 0, max: 0 }; + valueAxisScaleFactoryConfiguration.minimalRange = { min: 0, max: 0 }; } - this._yAxisScaleFactory = new RangeBasedScaleFactory(yAxisScaleFactoryConfiguration); + const valueAxisScaleFactory = new RangeBasedScaleFactory(valueAxisScaleFactoryConfiguration); + + for (const point of data) { + indexAxisScaleFactory.processValue?.(point[indexAxis]); - for (const point of points) { - this._xAxisScaleFactory.processValue?.(point.x); - if (this._stackedY) { - for (const value of point.y) { - this._yAxisScaleFactory.processValue?.(value); + if (Array.isArray(point[valueAxis])) { + for (const value of point[valueAxis]) { + valueAxisScaleFactory.processValue?.(value); } } else { - this._yAxisScaleFactory.processValue?.(point.y); + valueAxisScaleFactory.processValue?.(point[valueAxis]); } } + if (this.isXIndexAxis) { + this._xAxisScaleFactory = indexAxisScaleFactory; + this._yAxisScaleFactory = valueAxisScaleFactory; + } else { + this._xAxisScaleFactory = valueAxisScaleFactory; + this._yAxisScaleFactory = indexAxisScaleFactory; + } + this._refreshScales(); } @@ -178,7 +205,9 @@ export class ChartRenderer { yAxisRenderer.render(this.yScale, this._chartDrawingZone); // Render the horizontal grid - renderHorizontalGrid(yAxisRenderer.getCurrentAxis(), this._chartDrawingZone.width); + if (this._renderGrid) { + renderHorizontalGrid(yAxisRenderer.getCurrentAxis(), this._chartDrawingZone.width); + } this.registerEvents(svg); } @@ -200,7 +229,7 @@ export class ChartRenderer { /** * Return the scale factory for the x-axis * - * @return {EnumerableBasedScaleFactory} the scale factory + * @return {EnumerableBasedScaleFactory|RangeBasedScaleFactory} the scale factory - enumerable scale if x is index axis, range otherwise */ get xAxisScaleFactory() { return this._xAxisScaleFactory; @@ -209,14 +238,14 @@ export class ChartRenderer { /** * Return the scale factory for the y-axis * - * @return {RangeBasedScaleFactory} the scale factory + * @return {RangeBasedScaleFactory|EnumerableBasedScaleFactory} the scale factory - range scale if y is value axis, enumerable otherwise */ get yAxisScaleFactory() { return this._yAxisScaleFactory; } /** - * States if the points are rendered as points or bars + * States if the data are rendered as points or bars * @return {boolean} true if points */ get isPoint() { @@ -272,6 +301,24 @@ export class ChartRenderer { return this._yScale; } + /** + * Returns the current index-axis scale + * + * @return {d3.AxisScale} the scale + */ + get indexAxisScale() { + return this.isXIndexAxis ? this._xScale : this._yScale; + } + + /** + * States whether x is index variable + * + * @return {boolean} true if x is index axis, false otherwise + */ + get isXIndexAxis() { + return this._indexAxis === X_AXIS_NAME; + } + /** * Recompute the scales with the current chart drawing zone * diff --git a/lib/public/components/common/chart/rendering/LineChartRenderer.js b/lib/public/components/common/chart/rendering/LineChartRenderer.js index cd02690f2b..2a5be86996 100644 --- a/lib/public/components/common/chart/rendering/LineChartRenderer.js +++ b/lib/public/components/common/chart/rendering/LineChartRenderer.js @@ -12,7 +12,7 @@ */ import { quadtree } from '/assets/d3.min.js'; -import { ChartRenderer } from './ChartRenderer.js'; +import { ChartRenderer, X_AXIS_NAME } from './ChartRenderer.js'; import { renderDatasetAsLine } from './dataset/renderDatasetAsLine.js'; import { renderDatasetAsPointCloud } from './dataset/renderDatasetAsPointCloud.js'; import { renderDatasetLegend } from './legend/renderDatasetLegend.js'; @@ -45,6 +45,9 @@ export class LineChartRenderer extends ChartRenderer { */ constructor(configuration, points) { super(configuration, points); + if (!this.isXIndexAxis) { + throw new Error(`LineChartRendered only supports '${X_AXIS_NAME}' as index axis`); + } this._pointHoverMargin = configuration.pointHoverMargin || DEFAULT_POINT_HOVER_MARGIN; this._computeHoveredPoint = new Array(this._datasets.length).fill(0); @@ -66,14 +69,14 @@ export class LineChartRenderer extends ChartRenderer { /** @type {PointLocator} */ const pointLocator = { getX: ({ x }) => this.xScale(x), - getY: this._stackedY ? ({ y }) => this.yScale(y[datasetIndex]) : ({ y }) => this.yScale(y), + getY: this._stackedValues ? ({ y }) => this.yScale(y[datasetIndex]) : ({ y }) => this.yScale(y), }; const { line: lineConfiguration, point: pointConfiguration, legend: legendConfiguration } = this._datasets[datasetIndex] || {}; // Display datasets - const points = this._points.filter((point) => (pointLocator.getY(point) ?? null) !== null); + const points = this._data.filter((point) => (pointLocator.getY(point) ?? null) !== null); // Display line if (lineConfiguration) { @@ -99,7 +102,7 @@ export class LineChartRenderer extends ChartRenderer { const datasetQuadtree = quadtree() .x(pointLocator.getX) .y(pointLocator.getY) - .addAll(this._points); + .addAll(this._data); /** * Compute the point hovered by the mouse and call the onPointHover function accordingly diff --git a/lib/public/components/common/chart/rendering/dataset/renderDatasetAsBars.js b/lib/public/components/common/chart/rendering/dataset/renderDatasetAsBars.js index 07cd72e2be..8f78f1d09c 100644 --- a/lib/public/components/common/chart/rendering/dataset/renderDatasetAsBars.js +++ b/lib/public/components/common/chart/rendering/dataset/renderDatasetAsBars.js @@ -13,6 +13,30 @@ import { select } from '/assets/d3.min.js'; +export const BarPattern = { + DIAGONAL_STRIPES: 'diagonalStripes', +}; + +/** + * Add pattern defintion to SVG + * + * @param {SVGElement} svg svg element + * @return {void} + */ +const addPatternsDefintionToSVG = (svg) => { + const defs = select(svg).append('defs'); + defs.append('pattern') + .attr('id', BarPattern.DIAGONAL_STRIPES) + .attr('width', 5) + .attr('height', 5) + .attr('patternUnits', 'userSpaceOnUse') + .append('path') + .attr('d', 'M 0,5 L 5,0') + .attr('stroke', 'black') + .attr('opacity', 0.5) + .attr('stroke-width', 1); +}; + /** * @typedef BarGraphConfiguration * @property {string} [stroke] the stroke color of the bar @@ -20,35 +44,68 @@ import { select } from '/assets/d3.min.js'; */ /** - * Render a list of points as a line graph (eventually filled) + * Render a list of bars as a bar graph * * @param {SVGElement} svg the svg in which graph must be rendered - * @param {Point[]} points the points to display - * @param {number} barWidth the width of bars to display - * @param {BoundingBox} chartDrawingZone the chart drawing zone - * @param {PointLocator} pointLocator locator to compute the points coordinates + * @param {ChartBar[]} bars the bars to display + * @param {ChartBarPropertiesProvider} barPropertiesProvider provider to compute the bars coordinates as well as width and height * @param {BarGraphConfiguration} [configuration] the bar graph configuration * @return {void} */ export const renderDatasetAsBars = ( svg, - points, - barWidth, - chartDrawingZone, - pointLocator, + bars, + barPropertiesProvider, configuration, ) => { const { fill, stroke } = configuration; + /** + * Get bar fill + * @param {ChartBar} bar a bar + * @return {string} fill + */ + const getFill = (bar) => bar.fill ?? fill ?? 'none'; + + /** + * Get bar stroke + * @param {ChartBar} bar a bar + * @return {string} stroke + */ + const getStroke = (bar) => bar.stroke ?? stroke ?? 'none'; + + const { getWidth, getHeight, getX, getY } = barPropertiesProvider; + + const anyPattern = bars.map(({ pattern }) => pattern).filter((pattern) => pattern)?.length > 0; + if (anyPattern) { + addPatternsDefintionToSVG(svg); + } + select(svg) .append('g') .selectAll('rect') - .data(points) - .join('rect') - .attr('width', barWidth) - .attr('height', (point) => chartDrawingZone.top + chartDrawingZone.height - pointLocator.getY(point)) - .attr('x', pointLocator.getX) - .attr('y', pointLocator.getY) - .attr('fill', fill ?? 'none') - .attr('stroke', stroke ?? 'none'); + .data(bars) + .enter() + .append('g') + .each(function (bar) { + select(this).append('rect') + .attr('width', getWidth) + .attr('height', getHeight) + .attr('x', getX) + .attr('y', getY) + .attr('fill', getFill) + .attr('stroke', getStroke) + .attr('opacity', bar.opacity ?? 'none'); + + const { pattern } = bar; + if (pattern) { + select(this).append('rect') + .attr('width', getWidth) + .attr('height', getHeight) + .attr('x', getX) + .attr('y', getY) + .attr('fill', `url(#${pattern})`) + .attr('stroke', 'none'); + } + }); }; diff --git a/lib/public/components/common/chart/structures/BarPropertiesProvider.js b/lib/public/components/common/chart/structures/BarPropertiesProvider.js new file mode 100644 index 0000000000..8d45e26a79 --- /dev/null +++ b/lib/public/components/common/chart/structures/BarPropertiesProvider.js @@ -0,0 +1,28 @@ +/** + * @license + * Copyright CERN and copyright holders of ALICE O2. This software is + * distributed under the terms of the GNU General Public License v3 (GPL + * Version 3), copied verbatim in the file "COPYING". + * + * See http://alice-o2.web.cern.ch/license for full licensing information. + * + * 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. + */ + +/** + * @callback ChartBarToChartBarProperty + * @param {ChartBar} bar + * @return {number|number[]} bar property + */ + +/** + * @typedef ChartBarPropertiesProvider + * @property {ChartBarToChartBarProperty} getX function to get position along x-axis + * if x is independnet variable and horizontal boundary(-ies) otherwise + * @property {ChartBarToChartBarProperty} getY function to get horizontal boundary(-ies) along y-axis + * if y is dependnet variable and position otherwise + * @property {ChartBarToChartBarProperty} getHeight function to get the height (size along y-axis) of a given bar + * @property {ChartBarToChartBarProperty} getWidth function to get the width (size along x-axis) of a given bar + */ diff --git a/lib/public/components/common/chart/structures/ChartBar.js b/lib/public/components/common/chart/structures/ChartBar.js new file mode 100644 index 0000000000..01cec9b375 --- /dev/null +++ b/lib/public/components/common/chart/structures/ChartBar.js @@ -0,0 +1,19 @@ +/** + * @license + * Copyright CERN and copyright holders of ALICE O2. This software is + * distributed under the terms of the GNU General Public License v3 (GPL + * Version 3), copied verbatim in the file "COPYING". + * + * See http://alice-o2.web.cern.ch/license for full licensing information. + * + * 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. + */ + +/** + * @typedef ChartBar + * @property {number|number[]} x position along x-axis if x is independnet variable and horizontal boundary(-ies) otherwise + * @property {number|number[]} y horizontal boundary(-ies) along y-axis if y is dependnet variable and position otherwise + * @type {VisualProperties} + */ diff --git a/lib/public/components/common/chart/structures/DrawableData.js b/lib/public/components/common/chart/structures/DrawableData.js new file mode 100644 index 0000000000..7d7c70174b --- /dev/null +++ b/lib/public/components/common/chart/structures/DrawableData.js @@ -0,0 +1,17 @@ +/** + * @license + * Copyright CERN and copyright holders of ALICE O2. This software is + * distributed under the terms of the GNU General Public License v3 (GPL + * Version 3), copied verbatim in the file "COPYING". + * + * See http://alice-o2.web.cern.ch/license for full licensing information. + * + * 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. + */ + +/** + * @typedef DrawableData + * @type {(Point+Optional)|(Bar+Optional)} + */ diff --git a/lib/public/components/common/chart/structures/VisualProperties.js b/lib/public/components/common/chart/structures/VisualProperties.js new file mode 100644 index 0000000000..a9ab4aea9a --- /dev/null +++ b/lib/public/components/common/chart/structures/VisualProperties.js @@ -0,0 +1,19 @@ +/** + * @license + * Copyright CERN and copyright holders of ALICE O2. This software is + * distributed under the terms of the GNU General Public License v3 (GPL + * Version 3), copied verbatim in the file "COPYING". + * + * See http://alice-o2.web.cern.ch/license for full licensing information. + * + * 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. + */ + +/** + * @typedef VisualProperties + * @property {string} fill + * @property {string} stroke + * @property {string} opacity + */ diff --git a/lib/public/views/QcFlags/ForDataPass/QcFlagsForDataPassOverviewPage.js b/lib/public/views/QcFlags/ForDataPass/QcFlagsForDataPassOverviewPage.js index 08f84c54a2..06c9deb318 100644 --- a/lib/public/views/QcFlags/ForDataPass/QcFlagsForDataPassOverviewPage.js +++ b/lib/public/views/QcFlags/ForDataPass/QcFlagsForDataPassOverviewPage.js @@ -16,10 +16,13 @@ import { h } from '/js/src/index.js'; import { frontLink } from '../../../components/common/navigation/frontLink.js'; import { estimateDisplayableRowsCount } from '../../../utilities/estimateDisplayableRowsCount.js'; import { table } from '../../../components/common/table/table.js'; -import { paginationComponent } from '../../../components/Pagination/paginationComponent.js'; import { qcFlagsActiveColumns } from '../ActiveColumns/qcFlagsActiveColumns.js'; import { qcFlagCreationPanelLink } from '../../../components/qcFlags/qcFlagsPagesButtons.js'; import { qcFlagsBreadcrumbs } from '../../../components/qcFlags/qcFlagsBreadcrumbs.js'; +import { qcFlagsChartComponent } from '../qcFlagsVisualization/qcFlagsChartComponent.js'; +import { mergeRemoteData } from '../../../utilities/mergeRemoteData.js'; +import errorAlert from '../../../components/common/errorAlert.js'; +import spinner from '../../../components/common/spinner.js'; const TABLEROW_HEIGHT = 35; // Estimate of the navbar and pagination elements height total; Needs to be updated in case of changes; @@ -63,28 +66,38 @@ export const QcFlagsForDataPassOverviewPage = ({ }; const { - items: qcFlags, + items: remoteQcFlags, sortModel, - pagination: paginationModel, } = qcFlagsForDataPassOverviewModel; return h( - '', + '.flex-column', { onremove: () => qcFlagsForDataPassOverviewModel.reset() }, [ h('.flex-row.justify-between.items-center', [ qcFlagsBreadcrumbs({ remoteDataPass, remoteRun, remoteDplDetector }), qcFlagCreationPanelLink({ dataPassId }, remoteRun, dplDetectorId, remoteDplDetectorsUserHasAccessTo), ]), + remoteRun.match({ + Failure: (errors) => errorAlert(errors), + Other: () => null, + }), + mergeRemoteData([remoteQcFlags, remoteRun]) + .match({ + Success: ([flags, run]) => qcFlagsChartComponent(flags, run), + Failure: () => null, + Loading: () => spinner({ size: 2, absolute: false }), + NotAsked: () => errorAlert([{ title: 'No data', detail: 'No QC flags or run data was asked for' }]), + }), + h('.w-100.flex-column', [ table( - qcFlags, + remoteQcFlags, activeColumns, { classes: '.table-sm' }, null, { sort: sortModel }, ), - paginationComponent(paginationModel), ]), ], ); diff --git a/lib/public/views/QcFlags/Overview/QcFlagsOverviewModel.js b/lib/public/views/QcFlags/Overview/QcFlagsOverviewModel.js index 1318ac2635..2f0f6d2039 100644 --- a/lib/public/views/QcFlags/Overview/QcFlagsOverviewModel.js +++ b/lib/public/views/QcFlags/Overview/QcFlagsOverviewModel.js @@ -47,6 +47,18 @@ export class QcFlagsOverviewModel extends OverviewPageModel { super.load(); } + /** + * Pagination for QC flags overview is temporarily disabled, because it interferes with QC flag visualtion. + * For now, only fetched flags are visualized and it may be misleading to have subset (because of pagination) of flags visualized. + */ + // eslint-disable-next-line valid-jsdoc + /** + * @inheritdoc + */ + getLoadParameters() { + return {}; + } + /** * Fetch DPL detector which QC flags should be fetched * @return {void} diff --git a/lib/public/views/QcFlags/qcFlagsVisualization/qcFlagsChartComponent.js b/lib/public/views/QcFlags/qcFlagsVisualization/qcFlagsChartComponent.js new file mode 100644 index 0000000000..04421157b3 --- /dev/null +++ b/lib/public/views/QcFlags/qcFlagsVisualization/qcFlagsChartComponent.js @@ -0,0 +1,110 @@ +/** + * @license + * Copyright CERN and copyright holders of ALICE O2. This software is + * distributed under the terms of the GNU General Public License v3 (GPL + * Version 3), copied verbatim in the file "COPYING". + * + * See http://alice-o2.web.cern.ch/license for full licensing information. + * + * 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. + */ + +import { barChartComponent } from '../../../components/common/chart/barChart/barChartComponent.js'; +import { BarPattern } from '../../../components/common/chart/rendering/dataset/renderDatasetAsBars.js'; +import { formatTimestamp } from '../../../utilities/formatting/formatTimestamp.js'; +import { ChartColors } from '../../Statistics/chartColors.js'; + +/** + * Create QC flags visualiation as horizontall bar chart + * + * @param {QualityControlFlag[]} flags flags order by createdAt in descending manner + * @param {Run} run the run associated with given flags + * @return {Component} the QC flags visualization + */ +export const qcFlagsChartComponent = (flags, run) => { + /** + * Get bars' data for effective and ineffectie periods of given QC flag + * Bars corresponging with effective periods have flagType.color + * For ineffective periods bars are additionally marked with stripes pattern + * @param {QcFlag} flag a QC flag + * @return {Bar[]} bars data + */ + const getBarsForEffectiveAndIneffectivePeriodsOfQcFlag = (flag) => { + const { from, to, effectivePeriods } = flag; + const ineffectivePeriods = []; + if (effectivePeriods.length > 0) { + if (from !== effectivePeriods[0].from) { + ineffectivePeriods.push({ from, to: effectivePeriods[0].from }); + } + + for (let index = 0; index < effectivePeriods.length - 1; index++) { + const effectivePeriod = effectivePeriods[index]; + const nextEffectivePeriod = effectivePeriods[index + 1]; + ineffectivePeriods.push({ from: effectivePeriod.to, to: nextEffectivePeriod.from }); + } + + const lastEffectivePeriod = effectivePeriods[effectivePeriods.length - 1]; + if (to !== lastEffectivePeriod.to) { + ineffectivePeriods.push({ from: lastEffectivePeriod.to, to }); + } + } else { + ineffectivePeriods.push({ from, to }); + } + + /** + * Get bar's parameters which are the same for effective periods and ineffective period + * @param {Period} period period + * @return {{x: [number, number], y: string, fill: string }} parameters + */ + const getCommonBarParameters = ({ from, to }) => ({ + y: `${flag.flagType.name} (id:${flag.id})`, + x: [ + from ?? run.timeTrgStart ?? run.timeO2Start, + to ?? run.timeTrgEnd ?? run.timeO2End, + ], + fill: flag.flagType.color, + }); + + return [ + ...effectivePeriods.map(getCommonBarParameters), + ...ineffectivePeriods.map((period) => ({ + ...getCommonBarParameters(period), + pattern: BarPattern.DIAGONAL_STRIPES, + stroke: 'none', + opacity: 0.65, + })), + ]; + }; + + const barsData = [...flags].reverse().flatMap(getBarsForEffectiveAndIneffectivePeriodsOfQcFlag); + + return barChartComponent( + barsData, + { + placeholder: 'No data', + chartConfiguration: { + axis: { + x: { + label: 'Time', + ticks: { format: (t) => formatTimestamp(t, true) }, + min: run.timeTrgStart ?? run.timeO2Start, + max: run.timeTrgEnd ?? run.timeO2End, + }, + y: { + label: 'Flags', + }, + }, + datasets: { + bar: { + fill: ChartColors.Blue.dark, + stroke: ChartColors.Blue.light, + }, + }, + indexAxis: 'y', + renderGrid: false, + }, + }, + ); +}; diff --git a/lib/server/services/qualityControlFlag/QcFlagService.js b/lib/server/services/qualityControlFlag/QcFlagService.js index 0e482197f9..11de154656 100644 --- a/lib/server/services/qualityControlFlag/QcFlagService.js +++ b/lib/server/services/qualityControlFlag/QcFlagService.js @@ -586,6 +586,7 @@ class QcFlagService { { association: 'flagType' }, { association: 'createdBy' }, { association: 'verifications', include: [{ association: 'createdBy' }] }, + { association: 'effectivePeriods' }, ], where: { runNumber, @@ -752,6 +753,7 @@ class QcFlagService { { association: 'flagType' }, { association: 'createdBy' }, { association: 'verifications', include: [{ association: 'createdBy' }] }, + { association: 'effectivePeriods' }, ], where: { runNumber, diff --git a/test/api/qcFlags.test.js b/test/api/qcFlags.test.js index ded19b2442..cf53f52f89 100644 --- a/test/api/qcFlags.test.js +++ b/test/api/qcFlags.test.js @@ -49,6 +49,7 @@ module.exports = () => { createdBy: { id: 2, externalId: 456, name: 'Jan Jansen' }, flagTypeId: 13, flagType: { id: 13, name: 'Bad', method: 'Bad', mcReproducible: false, bad: true, archived: false, color: null }, + effectivePeriods: [], }); }); diff --git a/test/lib/server/services/qualityControlFlag/QcFlagService.test.js b/test/lib/server/services/qualityControlFlag/QcFlagService.test.js index 933a086bd9..18630942f1 100644 --- a/test/lib/server/services/qualityControlFlag/QcFlagService.test.js +++ b/test/lib/server/services/qualityControlFlag/QcFlagService.test.js @@ -61,6 +61,7 @@ const qcFlagWithId1 = { }, verifications: [], + effectivePeriods: [], }; module.exports = () => { diff --git a/test/public/qcFlags/forDataPassOverview.test.js b/test/public/qcFlags/forDataPassOverview.test.js index a51bb1c754..e93f95b386 100644 --- a/test/public/qcFlags/forDataPassOverview.test.js +++ b/test/public/qcFlags/forDataPassOverview.test.js @@ -19,7 +19,6 @@ const { pressElement, goToPage, validateTableData, - fillInput, expectUrlParams, waitForNavigation, } = require('../defaults.js'); @@ -107,54 +106,9 @@ module.exports = () => { }; await validateTableData(page, new Map(Object.entries(tableDataValidators))); - }); - - it('Should display the correct items counter at the bottom of the page', async () => { - await goToPage(page, 'qc-flags-for-data-pass', { queryParameters: { - dataPassId: 1, - runNumber: 106, - dplDetectorId: 1, - } }); - - await expectInnerText(page, '#firstRowIndex', '1'); - await expectInnerText(page, '#lastRowIndex', '3'); - await expectInnerText(page, '#totalRowsCount', '3'); - }); - - it('can set how many entires are available per page', async () => { - await goToPage(page, 'qc-flags-for-data-pass', { queryParameters: { - dataPassId: 1, - runNumber: 106, - dplDetectorId: 1, - } }); - - const amountSelectorId = '#amountSelector'; - await page.waitForSelector(amountSelectorId); - const amountSelectorButtonSelector = `${amountSelectorId} button`; - await pressElement(page, amountSelectorButtonSelector); - - await page.waitForSelector(`${amountSelectorId} .dropup-menu`); - - const amountItems5 = `${amountSelectorId} .dropup-menu .menu-item:first-child`; - await pressElement(page, amountItems5); - - await fillInput(page, `${amountSelectorId} input[type=number]`, 1111); - await page.waitForSelector(amountSelectorId); - }); - - it('notifies if table loading returned an error', async () => { - await goToPage(page, 'qc-flags-for-data-pass', { queryParameters: { - dataPassId: 1, - runNumber: 106, - dplDetectorId: 1, - } }); - - // eslint-disable-next-line no-return-assign, no-undef - await page.evaluate(() => model.qcFlags.forDataPassOverviewModel.pagination.itemsPerPage = 200); - // We expect there to be a fitting error message - const expectedMessage = 'Invalid Attribute: "query.page.limit" must be less than or equal to 100'; - await expectInnerText(page, '.alert-danger', expectedMessage); + const rects = await page.$$('svg rect'); + expect(rects).to.be.lengthOf(3); }); it('should inform when run quality was changed to bad', async () => { diff --git a/test/public/qcFlags/forSimulationPassOverview.test.js b/test/public/qcFlags/forSimulationPassOverview.test.js index 8fc7571dd8..1ee083d70d 100644 --- a/test/public/qcFlags/forSimulationPassOverview.test.js +++ b/test/public/qcFlags/forSimulationPassOverview.test.js @@ -140,19 +140,4 @@ module.exports = () => { await fillInput(page, `${amountSelectorId} input[type=number]`, 1111); await page.waitForSelector(amountSelectorId); }); - - it('notifies if table loading returned an error', async () => { - await goToPage(page, 'qc-flags-for-simulation-pass', { queryParameters: { - simulationPassId: 1, - runNumber: 106, - dplDetectorId: 1, - } }); - - // eslint-disable-next-line no-return-assign, no-undef - await page.evaluate(() => model.qcFlags.forSimulationPassOverviewModel.pagination.itemsPerPage = 200); - - // We expect there to be a fitting error message - const expectedMessage = 'Invalid Attribute: "query.page.limit" must be less than or equal to 100'; - await expectInnerText(page, '.alert-danger', expectedMessage); - }); }; From ac219f9710c34ae8b149d8765947903617eb8318 Mon Sep 17 00:00:00 2001 From: Martin Boulais <31805063+martinboulais@users.noreply.github.com> Date: Sun, 10 Nov 2024 21:24:48 +0100 Subject: [PATCH 5/5] Release 1.3.0 (#1798) --- CHANGELOG.md | 13 +++++++++++++ database/CHANGELOG.md | 4 ++++ package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6088e29386..03a5f684fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.3.0](https://github.com/AliceO2Group/Bookkeeping/releases/tag/%40aliceo2%2Fbookkeeping%401.3.0) +* Notable changes for users: + * Fixed physical constants values which resulted in wrong AVG center of mass energy + * Minor improvements to environments configuration display + * Table is now sorted alphabetically + * Table columns are not truncated anymore + * Use proper configuration for CCDB synchronization period and not monalisa one + * Added visualization of QC flags for data pass +* Notable changes for developers: + * Extended chart renderers: + * both x and y can be configured as index axis + * each bar in bar chart can have specific visual properties: color, stroke, pattern + ## [1.2.0](https://github.com/AliceO2Group/Bookkeeping/releases/tag/%40aliceo2%2Fbookkeeping%401.2.0) * Notable changes for users: * Prevented the QC summary color from changing to gray after more than one verification diff --git a/database/CHANGELOG.md b/database/CHANGELOG.md index b57c900a76..527caeed98 100644 --- a/database/CHANGELOG.md +++ b/database/CHANGELOG.md @@ -1,3 +1,7 @@ +## [1.3.0](https://github.com/AliceO2Group/Bookkeeping/releases/tag/%40aliceo2%2Fbookkeeping%401.3.0) +* Changes made to the database + * Fixed physical constants values in database + ## [1.1.0](https://github.com/AliceO2Group/Bookkeeping/releases/tag/%40aliceo2%2Fbookkeeping%401.1.0) * Changes made to the database * Added columns first_tf_timestamp and last_tf_timestamp to runs table diff --git a/package-lock.json b/package-lock.json index e147615469..67f2aa6eb8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@aliceo2/bookkeeping", - "version": "1.2.0", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@aliceo2/bookkeeping", - "version": "1.2.0", + "version": "1.3.0", "bundleDependencies": [ "@aliceo2/web-ui", "@grpc/grpc-js", diff --git a/package.json b/package.json index 1c2dbd9345..f020370410 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@aliceo2/bookkeeping", - "version": "1.2.0", + "version": "1.3.0", "author": "ALICEO2", "scripts": { "coverage": "nyc npm test && npm run coverage:report",