diff --git a/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js b/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js index e6c3cf564f..dbd1bc8c3b 100644 --- a/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js +++ b/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js @@ -48,17 +48,47 @@ export class RunsPerLhcPeriodOverviewModel extends RunsWithQcModel { this._syncDetectors$.bubbleTo(this); + this._lhcPeriodId = null; + this._lhcPeriodStatistics$ = new ObservableData(RemoteData.notAsked()); + this._lhcPeriodStatistics$.bubbleTo(this); + this._tabbedPanelModel = new RunsPerLhcPeriodTabbedPanelModel(); this._tabbedPanelModel.bubbleTo(this); } + /** + * Fetch LHC period data which runs are fetched + * @return {Promise} promise + */ + async _fetchLhcPeriod() { + this._lhcPeriodStatistics$.setCurrent(RemoteData.loading()); + try { + const { data: [lhcPeriodStatistics] } = await jsonFetch(`/api/lhcPeriodsStatistics?filter[ids][]=${this._lhcPeriodId}`); + this._lhcPeriodStatistics$.setCurrent(RemoteData.success(lhcPeriodStatistics)); + } catch (error) { + this._lhcPeriodStatistics$.setCurrent(RemoteData.failure(error)); + } + } + + /** + * @inheritdoc + */ + async load() { + if (!this._lhcPeriodId) { + return; + } + + await this._fetchLhcPeriod(); + super.load(); + } + /** * @inheritdoc */ getRootEndpoint() { return buildUrl(super.getRootEndpoint(), { filter: { - lhcPeriods: this._lhcPeriodName, + lhcPeriodIds: [this._lhcPeriodId], runQualities: 'good', definitions: 'PHYSICS', }, @@ -66,20 +96,30 @@ export class RunsPerLhcPeriodOverviewModel extends RunsWithQcModel { } /** - * Get name of current lhc period which runs are fetched + * Get LHC period which runs are fetched + */ + get lhcPeriodStatistics() { + return this._lhcPeriodStatistics$.getCurrent(); + } + + /** + * Set id of current LHC period which runs are fetched + * + * @param {string} lhcPeriodId id of a LHC period */ - get lhcPeriodName() { - return this._lhcPeriodName; + set lhcPeriodId(lhcPeriodId) { + this._lhcPeriodId = lhcPeriodId; + this._tabbedPanelModel.lhcPeriodId = lhcPeriodId; } /** - * Set name of current lhc period which runs are fetched + * Set mcReproducibleAsNotBad flag * - * @param {string} lhcPeriodName name of LHC period + * @param {boolean} mcReproducibleAsNotBad new value */ - set lhcPeriodName(lhcPeriodName) { - this._lhcPeriodName = lhcPeriodName; - this._tabbedPanelModel.lhcPeriodName = lhcPeriodName; + setMcReproducibleAsNotBad(mcReproducibleAsNotBad) { + super.setMcReproducibleAsNotBad(mcReproducibleAsNotBad); + this._tabbedPanelModel.mcReproducibleAsNotBad = mcReproducibleAsNotBad; } /** @@ -141,17 +181,18 @@ class RunsPerLhcPeriodTabbedPanelModel extends TabbedPanelModel { * @return {Promise} resolved once data are fetched */ async _fetchSynchronousQcSummary() { - if (this._lhcPeriodName) { + if (this._lhcPeriodId) { this.currentPanelData = RemoteData.loading(); this.notify(); try { - const { data: [lhcPeriod] } = await jsonFetch(`/api/lhcPeriodsStatistics?filter[names][]=${this._lhcPeriodName}`); - if (!lhcPeriod) { - this.currentPanelData = RemoteData.failure([{ title: `Cannot find LHC period with name '${this._lhcPeriodName}'` }]); - } else { - const { data: qcSummary } = await jsonFetch(`/api/qcFlags/summary?lhcPeriodId=${lhcPeriod.id}`); - this.currentPanelData = RemoteData.success(qcSummary); - } + const { data: qcSummary } = await jsonFetch(buildUrl( + '/api/qcFlags/summary', + { + lhcPeriodId: this._lhcPeriodId, + mcReproducibleAsNotBad: this._mcReproducibleAsNotBad, + }, + )); + this.currentPanelData = RemoteData.success(qcSummary); } catch (errors) { this.currentPanelData = RemoteData.failure(errors); } @@ -160,12 +201,22 @@ class RunsPerLhcPeriodTabbedPanelModel extends TabbedPanelModel { } /** - * Set LHC period name + * Set LHC period id + * + * @param {id} lhcPeriodId id of LHC period + */ + set lhcPeriodId(lhcPeriodId) { + this._lhcPeriodId = lhcPeriodId; + this._fetchCurrentPanelData(); + } + + /** + * Set mcReproducibleAsNotBad flag * - * @param {string} lhcPeriodName name of LHC period + * @param {boolean} mcReproducibleAsNotBad new value */ - set lhcPeriodName(lhcPeriodName) { - this._lhcPeriodName = lhcPeriodName; + set mcReproducibleAsNotBad(mcReproducibleAsNotBad) { + this._mcReproducibleAsNotBad = mcReproducibleAsNotBad; this._fetchCurrentPanelData(); } } diff --git a/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewPage.js b/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewPage.js index 1f97d022e6..0ecc55ef65 100644 --- a/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewPage.js +++ b/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewPage.js @@ -24,6 +24,12 @@ import { runDetectorsSyncQcActiveColumns } from '../ActiveColumns/runDetectorsSy import { tabbedPanelComponent } from '../../../components/TabbedPanel/tabbedPanelComponent.js'; import { RUNS_PER_LHC_PERIOD_PANELS_KEYS } from './RunsPerLhcPeriodOverviewModel.js'; import { PdpBeamType } from '../../../domain/enums/PdpBeamType.js'; +import { filtersPanelPopover } from '../../../components/Filters/common/filtersPanelPopover.js'; +import { runNumbersFilter } from '../../../components/Filters/RunsFilter/runNumbersFilter.js'; +import { mergeRemoteData } from '../../../utilities/mergeRemoteData.js'; +import errorAlert from '../../../components/common/errorAlert.js'; +import spinner from '../../../components/common/spinner.js'; +import { mcReproducibleAsNotBadToggle } from '../mcReproducibleAsNotBadToggle.js'; import { exportTriggerAndModal } from '../../../components/common/dataExport/exportTriggerAndModal.js'; const TABLEROW_HEIGHT = 62; @@ -53,44 +59,29 @@ export const RunsPerLhcPeriodOverviewPage = ({ runs: { perLhcPeriodOverviewModel const { items: remoteRuns, + lhcPeriodStatistics: remoteLhcPeriodStatistics, onlineDetectors: remoteOnlineDetectors, syncDetectors: remoteSyncDetectors, - lhcPeriodName, displayOptions, sortModel, tabbedPanelModel, + mcReproducibleAsNotBad, } = perLhcPeriodOverviewModel; - const activeColumns = { - ...runsActiveColumns, - ...remoteRuns.match({ - Success: (runs) => runs.some((run) => run.pdpBeamType === PdpBeamType.LEAD_LEAD) - ? inelasticInteractionRateActiveColumnsForPbPb - : {}, - Other: () => {}, - }), - ...remoteRuns.match({ - Success: (runs) => runs.some((run) => run.pdpBeamType === PdpBeamType.PROTON_PROTON) - ? inelasticInteractionRateActiveColumnsForProtonProton - : {}, - Other: () => {}, - }), - }; - /** * Render runs table with given detectors' active columns configuration * - * @param {object} detectorsActiveColumns active columns + * @param {object} activeColumns common acctivte column + * @param {object} detectorsActiveColumns detectors specific columns * @return {Component} table with pagination */ - const getTableWithGivenDetectorsColumns = (detectorsActiveColumns) => + const getTableWithGivenDetectorsColumns = (activeColumns, detectorsActiveColumns) => table( /* * Columns depends on detectors' list, it's not useful to render the table when detectors are missing - * TODO replace by RemoteData merging */ - remoteOnlineDetectors.match({ Success: () => remoteRuns, Other: () => remoteOnlineDetectors }), + remoteRuns, { ...activeColumns, ...detectorsActiveColumns, @@ -104,37 +95,69 @@ export const RunsPerLhcPeriodOverviewPage = ({ runs: { perLhcPeriodOverviewModel { sort: sortModel }, ); - return h('.intermediate-flex-column', [ - h('.flex-row.justify-between.g2', [ - h('h2', `Good, physics runs of ${lhcPeriodName}`), - exportTriggerAndModal(perLhcPeriodOverviewModel.exportModel, modalModel), - ]), - ...tabbedPanelComponent( - tabbedPanelModel, - { - [RUNS_PER_LHC_PERIOD_PANELS_KEYS.DETECTOR_QUALITIES]: 'Qualities of detectors', - [RUNS_PER_LHC_PERIOD_PANELS_KEYS.SYNCHRONOUS_FLAGS]: 'Synchronous QC flags', - }, - { - [RUNS_PER_LHC_PERIOD_PANELS_KEYS.DETECTOR_QUALITIES]: - () => getTableWithGivenDetectorsColumns(runDetectorsQualitiesActiveColumns( - remoteOnlineDetectors.match({ Success: (payload) => payload, Other: () => [] }), - { profiles: 'runsPerLhcPeriod' }, - )), + return h( + '.intermediate-flex-column', + { onremove: () => perLhcPeriodOverviewModel.reset(false) }, + mergeRemoteData([remoteLhcPeriodStatistics, remoteRuns]).match({ + NotAsked: () => null, + Failure: (errors) => errorAlert(errors), + Loading: () => spinner(), + Success: ([lhcPeriodStatistics, runs]) => { + const activeColumns = { + ...runsActiveColumns, + ...runs.some((run) => run.pdpBeamType === PdpBeamType.LEAD_LEAD) ? inelasticInteractionRateActiveColumnsForPbPb : {}, + ...runs.some((run) => run.pdpBeamType === PdpBeamType.PROTON_PROTON) + ? inelasticInteractionRateActiveColumnsForProtonProton : {}, + }; - [RUNS_PER_LHC_PERIOD_PANELS_KEYS.SYNCHRONOUS_FLAGS]: - (remoteSynchronousQcSummary) => getTableWithGivenDetectorsColumns(runDetectorsSyncQcActiveColumns( - remoteSyncDetectors.match({ Success: (payload) => payload, Other: () => [] }), + return [ + h('.flex-row.justify-between.items-center.g2', [ + filtersPanelPopover(perLhcPeriodOverviewModel, activeColumns, { profile: 'runsPerLhcPeriod' }), + h('.pl2#runOverviewFilter', runNumbersFilter(perLhcPeriodOverviewModel.filteringModel.get('runNumbers'))), + h('h2', `Good, physics runs of ${lhcPeriodStatistics.lhcPeriod.name}`), + mcReproducibleAsNotBadToggle( + mcReproducibleAsNotBad, + () => perLhcPeriodOverviewModel.setMcReproducibleAsNotBad(!mcReproducibleAsNotBad), + ), + exportTriggerAndModal(perLhcPeriodOverviewModel.exportModel, modalModel), + ]), + ...tabbedPanelComponent( + tabbedPanelModel, { - profiles: 'runsPerLhcPeriod', - qcSummary: remoteSynchronousQcSummary.match({ Success: (qcSummary) => qcSummary, Other: () => null }), + [RUNS_PER_LHC_PERIOD_PANELS_KEYS.DETECTOR_QUALITIES]: 'Qualities of detectors', + [RUNS_PER_LHC_PERIOD_PANELS_KEYS.SYNCHRONOUS_FLAGS]: 'Synchronous QC flags', }, - )), - }, - { - panelClass: ['scroll-auto'], + { + [RUNS_PER_LHC_PERIOD_PANELS_KEYS.DETECTOR_QUALITIES]: () => remoteOnlineDetectors.match({ + Success: (onlineDetectors) => getTableWithGivenDetectorsColumns( + activeColumns, + runDetectorsQualitiesActiveColumns(onlineDetectors, { profiles: 'runsPerLhcPeriod' }), + ), + NotAsked: () => null, + Failure: (errors) => errorAlert(errors), + Loading: () => spinner(), + }), + + [RUNS_PER_LHC_PERIOD_PANELS_KEYS.SYNCHRONOUS_FLAGS]: (remoteSynchronousQcSummary) => + mergeRemoteData([remoteSynchronousQcSummary, remoteSyncDetectors]).match({ + Success: ([synchronousQcSummary, syncDetectors]) => getTableWithGivenDetectorsColumns( + activeColumns, + runDetectorsSyncQcActiveColumns(syncDetectors, { + profiles: 'runsPerLhcPeriod', + qcSummary: synchronousQcSummary, + }), + ), + NotAsked: () => null, + Failure: (errors) => errorAlert(errors), + Loading: () => spinner(), + }), + }, + { panelClass: ['scroll-auto'] }, + ), + paginationComponent(perLhcPeriodOverviewModel.pagination), + ]; }, - ), - paginationComponent(perLhcPeriodOverviewModel.pagination), - ]); + }), + + ); }; diff --git a/lib/public/views/Runs/RunsModel.js b/lib/public/views/Runs/RunsModel.js index 88aec1ca89..8298ee2857 100644 --- a/lib/public/views/Runs/RunsModel.js +++ b/lib/public/views/Runs/RunsModel.js @@ -85,14 +85,14 @@ export class RunsModel extends Observable { /** * Load runs overview data * @param {object} params the parameters for the model - * @param {string} [params.lhcPeriodName] the name of the LHC period to display + * @param {string} [params.lhcPeriodId] the id of the LHC period to display * @param {string} [params.panel] the key of the panel to display * @return {void} */ - loadPerLhcPeriodOverview({ lhcPeriodName, panel }) { + loadPerLhcPeriodOverview({ lhcPeriodId, panel }) { this._perLhcPeriodOverviewModel.tabbedPanelModel.currentPanelKey = panel; if (!this._perLhcPeriodOverviewModel.pagination.isInfiniteScrollEnabled) { - this._perLhcPeriodOverviewModel.lhcPeriodName = lhcPeriodName; + this._perLhcPeriodOverviewModel.lhcPeriodId = lhcPeriodId; this._perLhcPeriodOverviewModel.load(); } } diff --git a/lib/public/views/lhcPeriods/ActiveColumns/lhcPeriodsActiveColumns.js b/lib/public/views/lhcPeriods/ActiveColumns/lhcPeriodsActiveColumns.js index 125cb30c2c..8d1eeeb19a 100644 --- a/lib/public/views/lhcPeriods/ActiveColumns/lhcPeriodsActiveColumns.js +++ b/lib/public/views/lhcPeriods/ActiveColumns/lhcPeriodsActiveColumns.js @@ -40,13 +40,13 @@ export const lhcPeriodsActiveColumns = { associatedRuns: { name: 'Runs', visible: true, - format: (_, { name, runsCount }) => + format: (_, { id, runsCount }) => runsCount === 0 ? 'No runs' : frontLink( badge(runsCount), 'runs-per-lhc-period', - { lhcPeriodName: name }, + { lhcPeriodId: id }, ), classes: 'w-10', }, diff --git a/test/public/defaults.js b/test/public/defaults.js index bdbcbdff88..95db4b5a86 100644 --- a/test/public/defaults.js +++ b/test/public/defaults.js @@ -781,15 +781,15 @@ module.exports.testTableSortingByColumn = async (page, columnId) => { const notOrderData = await getColumnCellsInnerTexts(page, columnId); // Sort in ASCENDING manner - await this.pressElement(page, `th#${columnId}`); + await this.pressElement(page, `th#${columnId}`, true); this.expectColumnValues(page, columnId, [...notOrderData].sort()); // Sort in DESCENDING manner - await this.pressElement(page, `th#${columnId}`); + await this.pressElement(page, `th#${columnId}`, true); this.expectColumnValues(page, columnId, [...notOrderData].sort().reverse()); // Revoke sorting - await this.pressElement(page, `th#${columnId}`); + await this.pressElement(page, `th#${columnId}`, true); this.expectColumnValues(page, columnId, notOrderData); }; diff --git a/test/public/runs/runsPerLhcPeriod.overview.test.js b/test/public/runs/runsPerLhcPeriod.overview.test.js index b58be68d5c..86a422c7a9 100644 --- a/test/public/runs/runsPerLhcPeriod.overview.test.js +++ b/test/public/runs/runsPerLhcPeriod.overview.test.js @@ -29,6 +29,7 @@ const { getInnerText, expectUrlParams, fillInput, + expectColumnValues, } = require('../defaults.js'); const { RUN_QUALITIES, RunQualities } = require('../../../lib/domain/enums/RunQualities.js'); const { resetDatabaseContent } = require('../../utilities/resetDatabaseContent.js'); @@ -73,7 +74,7 @@ module.exports = () => { }); it('loads the page successfully', async () => { - const response = await goToPage(page, 'runs-per-lhc-period', { queryParameters: { lhcPeriodName: 'LHC22a' } }); + const response = await goToPage(page, 'runs-per-lhc-period', { queryParameters: { lhcPeriodId: 1 } }); expect(response.status()).to.equal(200); @@ -152,9 +153,9 @@ module.exports = () => { await page.waitForSelector(`${amountSelectorId} .dropup-menu`); const amountItems5 = `${amountSelectorId} .dropup-menu .menu-item:first-child`; - await pressElement(page, amountItems5); - await expectInnerText(page, '.dropup button', 'Rows per page: 5 '); + await pressElement(page, amountItems5, true); await waitForTableLength(page, 4); + await expectInnerText(page, '.dropup button', 'Rows per page: 5 '); // Expect the custom per page input to have red border and text color if wrong value typed await fillInput(page, `${amountSelectorId} input[type=number]`, '1111'); @@ -190,8 +191,9 @@ module.exports = () => { const targetFileName = 'data.json'; // First export - await pressElement(page, EXPORT_RUNS_TRIGGER_SELECTOR); + await pressElement(page, EXPORT_RUNS_TRIGGER_SELECTOR, true); await page.waitForSelector('select.form-control', { timeout: 200 }); + await page.waitForSelector('option[value=runNumber]', { timeout: 200 }); await page.select('select.form-control', 'runQuality', 'runNumber', 'definition', 'lhcPeriod'); await expectInnerText(page, '#send:enabled', 'Export'); @@ -236,5 +238,19 @@ module.exports = () => { const expectedRunNumber = await getInnerText(await page.waitForSelector('tbody tr:first-of-type a')); await waitForNavigation(page, () => pressElement(page, 'tbody tr:first-of-type a')); expectUrlParams(page, { page: 'run-detail', runNumber: expectedRunNumber }); + await page.goBack() + }); + + it('should successfully apply detectors notBadFraction filters', async () => { + await pressElement(page, '#openFilterToggle', true); + + await page.waitForSelector('#inelasticInteractionRateAvg-operator'); + await page.select('#inelasticInteractionRateAvg-operator', '<='); + await fillInput(page, '#inelasticInteractionRateAvg-operand', '100000', ['change']); + await expectColumnValues(page, 'runNumber', ['56', '54']); + + await pressElement(page, '#openFilterToggle', true); + await pressElement(page, '#reset-filters', true); + await expectColumnValues(page, 'runNumber', ['105', '56', '54', '49']); }); };