diff --git a/lib/database/migrations/20241105090940-store-run-and-qcflags-timestamps-in-ms.js b/lib/database/migrations/20241105090940-store-run-and-qcflags-timestamps-in-ms.js new file mode 100644 index 0000000000..0ce56a4364 --- /dev/null +++ b/lib/database/migrations/20241105090940-store-run-and-qcflags-timestamps-in-ms.js @@ -0,0 +1,266 @@ +'use strict'; + +const TIME_START_END_TO_DATETIME_3 = ` + ALTER TABLE runs + CHANGE COLUMN time_start time_start DATETIME(3) AS (COALESCE(time_trg_start, time_trg_end)) VIRTUAL, + CHANGE COLUMN time_end time_end DATETIME(3) AS (COALESCE(time_trg_end, time_o2_end)) VIRTUAL; +`; + +const TIME_START_END_TO_DATETIME = ` + ALTER TABLE runs + CHANGE COLUMN time_start time_start DATETIME AS (COALESCE(time_trg_start, time_trg_end)) VIRTUAL, + CHANGE COLUMN time_end time_end DATETIME AS (COALESCE(time_trg_end, time_o2_end)) VIRTUAL; +`; + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + up: async (queryInterface, Sequelize) => queryInterface.sequelize.transaction(async (transaction) => { + await queryInterface.changeColumn( + 'runs', + 'time_o2_start', + { + type: Sequelize.DATE(3), + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'runs', + 'time_trg_start', + { + type: Sequelize.DATE(3), + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'runs', + 'first_tf_timestamp', + { + type: Sequelize.DATE(3), + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'runs', + 'last_tf_timestamp', + { + type: Sequelize.DATE(3), + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'runs', + 'time_trg_end', + { + type: Sequelize.DATE(3), + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'runs', + 'time_o2_end', + { + type: Sequelize.DATE(3), + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'quality_control_flags', + 'from', + { + type: Sequelize.DATE(3), + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'quality_control_flags', + 'to', + { + type: Sequelize.DATE(3), + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'quality_control_flags', + 'created_at', + { + type: Sequelize.DATE(3), + allowNull: false, + defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'), + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'quality_control_flags', + 'updated_at', + { + type: Sequelize.DATE(3), + allowNull: false, + defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'quality_control_flag_effective_periods', + 'from', + { + type: Sequelize.DATE(3), + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'quality_control_flag_effective_periods', + 'to', + { + type: Sequelize.DATE(3), + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'quality_control_flag_effective_periods', + 'created_at', + { + type: Sequelize.DATE(3), + allowNull: false, + defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'), + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'quality_control_flag_effective_periods', + 'updated_at', + { + type: Sequelize.DATE(3), + allowNull: false, + defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), + }, + { transaction }, + ); + + await queryInterface.sequelize.query(TIME_START_END_TO_DATETIME_3, { transaction }); + }), + + down: async (queryInterface, Sequelize) => queryInterface.sequelize.transaction(async (transaction) => { + await queryInterface.changeColumn( + 'runs', + 'time_o2_start', + { + type: Sequelize.DATE, + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'runs', + 'time_trg_start', + { + type: Sequelize.DATE, + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'runs', + 'first_tf_timestamp', + { + type: Sequelize.DATE, + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'runs', + 'last_tf_timestamp', + { + type: Sequelize.DATE, + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'runs', + 'time_trg_end', + { + type: Sequelize.DATE, + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'runs', + 'time_o2_end', + { + type: Sequelize.DATE, + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'quality_control_flags', + 'from', + { + type: Sequelize.DATE, + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'quality_control_flags', + 'to', + { + type: Sequelize.DATE, + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'quality_control_flags', + 'created_at', + { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'), + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'quality_control_flags', + 'updated_at', + { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'quality_control_flag_effective_periods', + 'from', + { + type: Sequelize.DATE, + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'quality_control_flag_effective_periods', + 'to', + { + type: Sequelize.DATE, + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'quality_control_flag_effective_periods', + 'created_at', + { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'), + }, + { transaction }, + ); + await queryInterface.changeColumn( + 'quality_control_flag_effective_periods', + 'updated_at', + { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.literal('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), + }, + { transaction }, + ); + + await queryInterface.sequelize.query(TIME_START_END_TO_DATETIME, { transaction }); + }), +}; diff --git a/lib/database/repositories/EnvironmentHistoryItemRepository.js b/lib/database/repositories/EnvironmentHistoryItemRepository.js index d12073d7f5..ca0b4910d2 100644 --- a/lib/database/repositories/EnvironmentHistoryItemRepository.js +++ b/lib/database/repositories/EnvironmentHistoryItemRepository.js @@ -29,8 +29,8 @@ const getHistoryDistributionQuery = ({ from, to }) => ` FROM environments e INNER JOIN environments_history_items AS ehi ON e.id = ehi.environment_id - WHERE e.updated_at >= '${timestampToMysql(from)}' - AND e.created_at < '${timestampToMysql(to)}' + WHERE e.updated_at >= '${timestampToMysql(from, true)}' + AND e.created_at < '${timestampToMysql(to, true)}' GROUP BY e.id HAVING FIND_IN_SET('DESTROYED', statusHistory) > 0 OR FIND_IN_SET('ERROR', statusHistory) > 0 ) AS statusHistoryByEnvironmentId diff --git a/lib/server/services/run/setO2StopOfLostRuns.js b/lib/server/services/run/setO2StopOfLostRuns.js index 98eeced267..439ae79e68 100644 --- a/lib/server/services/run/setO2StopOfLostRuns.js +++ b/lib/server/services/run/setO2StopOfLostRuns.js @@ -29,8 +29,8 @@ exports.setO2StopOfLostRuns = async (runNumbersOfRunningRuns, modificationTimePe WHERE time_o2_end IS NULL AND time_trg_end IS NULL AND COALESCE(time_trg_start, time_o2_start) IS NOT NULL - AND COALESCE(time_trg_start, time_o2_start) >= '${timestampToMysql(modificationTimePeriod.from)}' - AND COALESCE(time_trg_start, time_o2_start) < '${timestampToMysql(modificationTimePeriod.to)}' + AND COALESCE(time_trg_start, time_o2_start) >= '${timestampToMysql(modificationTimePeriod.from, true)}' + AND COALESCE(time_trg_start, time_o2_start) < '${timestampToMysql(modificationTimePeriod.to, true)}' `; if (runNumbersOfRunningRuns.length > 0) { fetchQuery += ` AND run_number NOT IN (${runNumbersOfRunningRuns.join(',')})`; diff --git a/lib/server/utilities/timestampToMysql.js b/lib/server/utilities/timestampToMysql.js index c41f0c840d..42e7eeb3e8 100644 --- a/lib/server/utilities/timestampToMysql.js +++ b/lib/server/utilities/timestampToMysql.js @@ -15,6 +15,11 @@ * Convert a UNIX timestamp (in ms) to a MySQL date expression * * @param {number} timestamp the timestamp to convert + * @param {boolean} [milliseconds=false] if true, milliseconds will be stored in database * @return {string} the resulting SQL date */ -exports.timestampToMysql = (timestamp) => new Date(timestamp).toISOString().slice(0, 19).replace('T', ' '); +exports.timestampToMysql = (timestamp, milliseconds = false) => { + let dateIso = new Date(timestamp).toISOString(); + dateIso = dateIso.slice(0, milliseconds ? 23 : 19); + return dateIso.replace('T', ' '); +}; diff --git a/test/api/qcFlags.test.js b/test/api/qcFlags.test.js index cf53f52f89..776c14868a 100644 --- a/test/api/qcFlags.test.js +++ b/test/api/qcFlags.test.js @@ -77,7 +77,7 @@ module.exports = () => { 1: { missingVerificationsCount: 3, mcReproducible: true, - badEffectiveRunCoverage: 0.3333, + badEffectiveRunCoverage: 0.3333333, explicitlyNotBadEffectiveRunCoverage: 0, }, 16: { @@ -99,8 +99,8 @@ module.exports = () => { 1: { missingVerificationsCount: 3, mcReproducible: true, - badEffectiveRunCoverage: 0.1111, - explicitlyNotBadEffectiveRunCoverage: 0.2222, + badEffectiveRunCoverage: 0.1111111, + explicitlyNotBadEffectiveRunCoverage: 0.2222222, }, 16: { badEffectiveRunCoverage: 0, @@ -121,7 +121,7 @@ module.exports = () => { 1: { missingVerificationsCount: 1, mcReproducible: false, - badEffectiveRunCoverage: 0.7222, + badEffectiveRunCoverage: 0.7222222, explicitlyNotBadEffectiveRunCoverage: 0, }, }, @@ -138,8 +138,8 @@ module.exports = () => { 7: { missingVerificationsCount: 1, mcReproducible: false, - badEffectiveRunCoverage: 0.1667, - explicitlyNotBadEffectiveRunCoverage: 0.8333, + badEffectiveRunCoverage: 0.1666667, + explicitlyNotBadEffectiveRunCoverage: 0.8333333, }, // ITS diff --git a/test/lib/server/services/qualityControlFlag/QcFlagService.test.js b/test/lib/server/services/qualityControlFlag/QcFlagService.test.js index 18630942f1..92d32903d2 100644 --- a/test/lib/server/services/qualityControlFlag/QcFlagService.test.js +++ b/test/lib/server/services/qualityControlFlag/QcFlagService.test.js @@ -150,7 +150,7 @@ module.exports = () => { 1: { missingVerificationsCount: 3, mcReproducible: true, - badEffectiveRunCoverage: 0.3333, + badEffectiveRunCoverage: 0.3333333, explicitlyNotBadEffectiveRunCoverage: 0, }, 16: { @@ -169,8 +169,8 @@ module.exports = () => { 1: { missingVerificationsCount: 3, mcReproducible: true, - badEffectiveRunCoverage: 0.1111, - explicitlyNotBadEffectiveRunCoverage: 0.2222, + badEffectiveRunCoverage: 0.1111111, + explicitlyNotBadEffectiveRunCoverage: 0.2222222, }, 16: { badEffectiveRunCoverage: 0, @@ -213,7 +213,7 @@ module.exports = () => { 1: { missingVerificationsCount: 0, mcReproducible: false, - badEffectiveRunCoverage: 0.0769, + badEffectiveRunCoverage: 0.0769231, explicitlyNotBadEffectiveRunCoverage: 0, }, }, @@ -230,7 +230,7 @@ module.exports = () => { 1: { missingVerificationsCount: 0, mcReproducible: false, - badEffectiveRunCoverage: 0.0769, + badEffectiveRunCoverage: 0.0769231, explicitlyNotBadEffectiveRunCoverage: 0, }, }, @@ -247,7 +247,7 @@ module.exports = () => { 1: { missingVerificationsCount: 1, mcReproducible: false, - badEffectiveRunCoverage: 0.7222, + badEffectiveRunCoverage: 0.7222222, explicitlyNotBadEffectiveRunCoverage: 0, }, }, @@ -265,8 +265,8 @@ module.exports = () => { 7: { missingVerificationsCount: 1, mcReproducible: false, - badEffectiveRunCoverage: 0.1667, - explicitlyNotBadEffectiveRunCoverage: 0.8333, + badEffectiveRunCoverage: 0.1666667, + explicitlyNotBadEffectiveRunCoverage: 0.8333333, }, // ITS diff --git a/test/public/flps/overview.test.js b/test/public/flps/overview.test.js index 6cd1c201aa..1762e4e4b2 100644 --- a/test/public/flps/overview.test.js +++ b/test/public/flps/overview.test.js @@ -20,7 +20,6 @@ const { getFirstRow, waitForTableLength, goToPage, - reloadPage, getInnerText, } = require('../defaults.js'); const { resetDatabaseContent } = require('../../utilities/resetDatabaseContent.js'); @@ -97,8 +96,6 @@ module.exports = () => { }); it('Should display the correct items counter at the bottom of the page', async () => { - await goToPage(page, 'flp-overview'); - await expectInnerText(page, '#firstRowIndex', '1'); await expectInnerText(page, '#lastRowIndex', '10'); await expectInnerText(page, '#totalRowsCount', '105'); @@ -106,7 +103,6 @@ module.exports = () => { it('can switch to infinite mode in amountSelector', async () => { const INFINITE_SCROLL_CHUNK = 19; - await reloadPage(page); // Wait fot the table to be loaded, it should have at least 2 rows (not loading) but less than 19 rows (which is infinite scroll chunk) await page.waitForSelector('table tbody tr:nth-child(2)'); @@ -186,18 +182,4 @@ module.exports = () => { model.flps.pagination.itemsPerPage = 10; }); }); - - /* - * It('can navigate to a flp detail page', async () => { - * table = await page.$$('tr'); - * firstRowId = await getFirstRow(table, page); - * const parsedFirstRowId = parseInt(firstRowId.slice('row'.length, firstRowId.length), 10); - * - * // We expect the entry page to have the same id as the id from the flp overview - * await pressElement(page, `#${firstRowId}`); - * await waitForTimeout(100); - * const redirectedUrl = await page.url(); - * expect(String(redirectedUrl).startsWith(`${url}/?page=flp-detail&id=${parsedFirstRowId}`)).to.be.true; - * }); - */ };