diff --git a/lib/domain/dtos/filters/LhcFillsFilterDto.js b/lib/domain/dtos/filters/LhcFillsFilterDto.js index 3338f44517..3fe9578388 100644 --- a/lib/domain/dtos/filters/LhcFillsFilterDto.js +++ b/lib/domain/dtos/filters/LhcFillsFilterDto.js @@ -14,6 +14,7 @@ const Joi = require('joi'); const { validateRange, RANGE_INVALID } = require('../../../utilities/rangeUtils'); const { validateBeamTypes, BEAM_TYPE_INVALID } = require('../../../utilities/beamTypeUtils'); const { validateTimeDuration } = require('../../../utilities/validateTime'); +const { FromToFilterDto } = require('./FromToFilterDto.js'); exports.LhcFillsFilterDto = Joi.object({ hasStableBeams: Joi.boolean(), @@ -23,6 +24,8 @@ exports.LhcFillsFilterDto = Joi.object({ }), runDuration: validateTimeDuration, beamDuration: validateTimeDuration, + stableBeamsStart: FromToFilterDto, + stableBeamsEnd: FromToFilterDto, schemeName: Joi.string().trim().max(64), beamTypes: Joi.string() .trim() diff --git a/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js b/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js index 9dbd83ae4f..b2657c8cfd 100644 --- a/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js +++ b/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js @@ -28,6 +28,7 @@ import { fillNumberFilter } from '../../../components/Filters/LhcFillsFilter/fil import { durationFilter } from '../../../components/Filters/LhcFillsFilter/durationFilter.js'; import { beamTypeFilter } from '../../../components/Filters/LhcFillsFilter/beamTypeFilter.js'; import { schemeNameFilter } from '../../../components/Filters/LhcFillsFilter/schemeNameFilter.js'; +import { timeRangeFilter } from '../../../components/Filters/common/filters/timeRangeFilter.js'; /** * List of active columns for a lhc fills table @@ -65,6 +66,14 @@ export const lhcFillsActiveColumns = { visible: true, size: 'w-8', format: (timestamp) => formatTimestamp(timestamp, false), + + /** + * Stable Beam start filter component + * + * @param {RunsOverviewModel} lhcFillsOverviewModel the lhcFills overview model + * @return {Component} the filter component + */ + filter: (lhcFillsOverviewModel) => timeRangeFilter(lhcFillsOverviewModel.filteringModel.get('stableBeamsStart').timeRangeInputModel), profiles: { lhcFill: true, environment: true, @@ -80,6 +89,14 @@ export const lhcFillsActiveColumns = { visible: true, size: 'w-8', format: (timestamp) => formatTimestamp(timestamp, false), + + /** + * Stable Beam end filter component + * + * @param {LhcFillsOverviewModel} lhcFillsOverviewModel the lhcFills overview model + * @return {Component} the filter component + */ + filter: (lhcFillsOverviewModel) => timeRangeFilter(lhcFillsOverviewModel.filteringModel.get('stableBeamsEnd').timeRangeInputModel), profiles: { lhcFill: true, environment: true, diff --git a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js index 910eacc644..c57ae69c25 100644 --- a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js +++ b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js @@ -19,6 +19,7 @@ import { OverviewPageModel } from '../../../models/OverviewModel.js'; import { addStatisticsToLhcFill } from '../../../services/lhcFill/addStatisticsToLhcFill.js'; import { BeamTypeFilterModel } from '../../../components/Filters/LhcFillsFilter/BeamTypeFilterModel.js'; import { TextComparisonFilterModel } from '../../../components/Filters/common/filters/TextComparisonFilterModel.js'; +import { TimeRangeFilterModel } from '../../../components/Filters/RunsFilter/TimeRangeFilter.js'; /** * Model for the LHC fills overview page @@ -39,6 +40,8 @@ export class LhcFillsOverviewModel extends OverviewPageModel { beamDuration: new TextComparisonFilterModel(), runDuration: new TextComparisonFilterModel(), hasStableBeams: new StableBeamFilterModel(), + stableBeamsStart: new TimeRangeFilterModel(), + stableBeamsEnd: new TimeRangeFilterModel(), beamTypes: new BeamTypeFilterModel(), schemeName: new RawTextFilterModel(), }); diff --git a/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js b/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js index 898ec5d3de..4315cf9e1a 100644 --- a/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js +++ b/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js @@ -47,12 +47,24 @@ class GetAllLhcFillsUseCase { let associatedStatisticsRequired = false; if (filter) { - const { hasStableBeams, fillNumbers, schemeName, beamDuration, runDuration, beamTypes } = filter; + const { hasStableBeams, fillNumbers, schemeName, beamDuration, stableBeamsStart, stableBeamsEnd, runDuration, beamTypes } = filter; if (hasStableBeams) { // For now, if a stableBeamsStart is present, then a beam is stable queryBuilder.where('stableBeamsStart').not().is(null); } + if (stableBeamsStart) { + const from = stableBeamsStart.from !== undefined ? stableBeamsStart.from : 0; + const to = stableBeamsStart.to !== undefined ? stableBeamsStart.to : new Date().getTime(); + queryBuilder.where('stableBeamsStart').between(from, to); + } + + if (stableBeamsEnd) { + const from = stableBeamsEnd.from !== undefined ? stableBeamsEnd.from : 0; + const to = stableBeamsEnd.to !== undefined ? stableBeamsEnd.to : new Date().getTime(); + queryBuilder.where('stableBeamsEnd').between(from, to); + } + if (fillNumbers) { const fillNumberCriteria = splitStringToStringsTrimmed(fillNumbers, SEARCH_ITEMS_SEPARATOR); diff --git a/test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js b/test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js index 852c559da3..8fbb5f2781 100644 --- a/test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js +++ b/test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js @@ -317,4 +317,60 @@ module.exports = () => { expect(lhcFills).to.be.an('array').and.lengthOf(0) }) + + it('should return an array with only \'from\' values given', async () => { + getAllLhcFillsDto.query = { + filter: { + stableBeamsStart: { + from: 1647867600000, + }, + stableBeamsEnd: { + from: 1647867600000, + }, + }, + }; + + const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto); + + expect(lhcFills).to.be.an('array'); + expect(lhcFills).to.have.lengthOf(3); + }); + + it('should return an array with only \'to\' values given', async () => { + getAllLhcFillsDto.query = { + filter: { + stableBeamsStart: { + to: 2000000000000 + }, + stableBeamsEnd: { + to: 2000000000000 + }, + }, + }; + + const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto); + + expect(lhcFills).to.be.an('array'); + expect(lhcFills).to.have.lengthOf(4); + }); + + it('should return an array with fills on certain timestamps', async () => { + getAllLhcFillsDto.query = { + filter: { + stableBeamsStart: { + from: 1647867600000, + to: 1647867600000, + }, + stableBeamsEnd: { + from: 1647961200000, + to: 1647961200000, + }, + }, + }; + + const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto); + + expect(lhcFills).to.be.an('array'); + expect(lhcFills).to.have.lengthOf(3); + }); }; diff --git a/test/public/lhcFills/overview.test.js b/test/public/lhcFills/overview.test.js index 2d1f72bb28..d93daeee5e 100644 --- a/test/public/lhcFills/overview.test.js +++ b/test/public/lhcFills/overview.test.js @@ -26,6 +26,8 @@ const { openFilteringPanel, expectAttributeValue, fillInput, + getPeriodInputsSelectors, + getPopoverSelector, } = require('../defaults.js'); const { resetDatabaseContent } = require('../../utilities/resetDatabaseContent.js'); @@ -161,16 +163,19 @@ module.exports = () => { }); it('fill dropdown menu should be correct', async() => { - // activate the popover - await pressElement(page, `#row6-fillNumber-text > div:nth-child(1) > div:nth-child(2)`) - await page.waitForSelector(`body > div:nth-child(3) > div:nth-child(1)`); - await expectInnerText(page, `#copy-6 > div:nth-child(1)`, 'Copy Fill Number') + const popoverTrigger = '#row6-fillNumber-text > div:nth-child(1) > div:nth-child(2)'; - await expectLink(page, 'body > div:nth-child(4) > div:nth-child(1) > div:nth-child(1) > div:nth-child(1) > a:nth-child(3)', { + await pressElement(page, popoverTrigger); + await expectInnerText(page, '#copy-6 > div:nth-child(1)', 'Copy Fill Number'); + + const popoverSelector = await getPopoverSelector(await page.waitForSelector(popoverTrigger)); + + + await expectLink(page, `${popoverSelector} a:nth-of-type(2)`, { href: `http://localhost:4000/?page=log-create&lhcFillNumbers=6`, innerText: ' Add log to this fill' }) // disable the popover - await pressElement(page, `#row6-fillNumber-text > div:nth-child(1) > div:nth-child(2)`) + await pressElement(page, popoverTrigger) }) it('can set how many lhcFills are available per page', async () => { @@ -272,12 +277,14 @@ module.exports = () => { it('should successfully display filter elements', async () => { const filterSBExpect = { selector: '.stableBeams-filter .w-30', value: 'Stable Beams Only' }; const filterFillNRExpect = {selector: 'div.items-baseline:nth-child(1) > div:nth-child(1)', value: 'Fill #'}; - const filterSBDurationExpect = {selector: 'div.items-baseline:nth-child(3) > div:nth-child(1)', value: 'SB Duration'}; + const filterSBStartExpect = {selector: 'div.items-baseline:nth-child(2) > div:nth-child(1)', value: 'SB START'}; + const filterSBEndExpect = {selector: 'div.items-baseline:nth-child(3) > div:nth-child(1)', value: 'SB END'}; + const filterSBDurationExpect = {selector: 'div.items-baseline:nth-child(5) > div:nth-child(1)', value: 'SB Duration'}; const filterSBDurationPlaceholderExpect = {selector: '#beam-duration-filter-operand', value: 'e.g 16:14:15 (HH:MM:SS)'} - const filterRunDurationExpect = {selector: 'div.flex-row:nth-child(4) > div:nth-child(1)', value: 'Total runs duration'} + const filterRunDurationExpect = {selector: 'div.flex-row:nth-child(6) > div:nth-child(1)', value: 'Total runs duration'} const filterRunDurationPlaceholderExpect = {selector: '#run-duration-filter-operand', value: 'e.g 16:14:15 (HH:MM:SS)'}; const filterSBDurationOperatorExpect = { value: true }; - const filterBeamTypeExpect = {selector: 'div.flex-row:nth-child(5) > div:nth-child(1)', value: 'Beam Type'} + const filterBeamTypeExpect = {selector: 'div.flex-row:nth-child(7) > div:nth-child(1)', value: 'Beam Type'} const filterSchemeNamePlaceholderExpect = {selector: '.fillingSchemeName-filter input', value: 'e.g. Single_12b_8_1024_8_2018'} await goToPage(page, 'lhc-fill-overview'); @@ -287,6 +294,8 @@ module.exports = () => { expect(await page.evaluate(() => document.querySelector('#beam-duration-filter-operator > option:nth-child(3)').selected)).to.equal(filterSBDurationOperatorExpect.value); await expectInnerText(page, filterSBExpect.selector, filterSBExpect.value); await expectInnerText(page, filterFillNRExpect.selector, filterFillNRExpect.value); + await expectInnerText(page, filterSBStartExpect.selector, filterSBStartExpect.value); + await expectInnerText(page, filterSBEndExpect.selector, filterSBEndExpect.value); await expectInnerText(page, filterSBDurationExpect.selector, filterSBDurationExpect.value); await expectAttributeValue(page, filterSBDurationPlaceholderExpect.selector, 'placeholder', filterSBDurationPlaceholderExpect.value); await expectInnerText(page, filterRunDurationExpect.selector, filterRunDurationExpect.value); @@ -354,6 +363,69 @@ module.exports = () => { await waitForTableLength(page, 2); }); + it('should successfully apply stableBeamStart filter', async () => { + const popoverTrigger = '.stableBeamsStart-filter .popover-trigger'; + + await goToPage(page, 'lhc-fill-overview'); + await waitForTableLength(page, 5); + await page.waitForSelector('.column-stableBeamsStart'); + + const filterButton = await page.waitForSelector('#openFilterToggle'); + const popoverKey = await filterButton.evaluate((button) => { + return button.parentElement.getAttribute('data-popover-key'); + }); + + const filterPanelSelector = `.popover[data-popover-key="${popoverKey}"]`; + + await openFilteringPanel(page); + await page.waitForSelector(filterPanelSelector, { visible: true }); + await page.waitForSelector(popoverTrigger); + + const popOverSelector = await getPopoverSelector(await page.$(popoverTrigger)); + const { fromDateSelector, toDateSelector, fromTimeSelector, toTimeSelector } = getPeriodInputsSelectors(popOverSelector); + console.log({ fromDateSelector, toDateSelector, fromTimeSelector, toTimeSelector }); + + + await fillInput(page, fromDateSelector, '2019-08-08', ['change']); + await fillInput(page, toDateSelector, '2019-08-08', ['change']); + await fillInput(page, fromTimeSelector, '10:00', ['change']); + await fillInput(page, toTimeSelector, '12:00', ['change']); + + await openFilteringPanel(page); + await pressElement(page, popoverTrigger); + await waitForTableLength(page, 1); + }); + + it('should successfully apply stableBeamEnd filter', async () => { + const popoverTrigger = '.stableBeamsEnd-filter .popover-trigger'; + + await goToPage(page, 'lhc-fill-overview'); + await waitForTableLength(page, 5); + await page.waitForSelector('.column-stableBeamsEnd'); + + const filterButton = await page.waitForSelector('#openFilterToggle'); + const popoverKey = await filterButton.evaluate((button) => { + return button.parentElement.getAttribute('data-popover-key'); + }); + + const filterPanelSelector = `.popover[data-popover-key="${popoverKey}"]`; + + await openFilteringPanel(page); + await page.waitForSelector(filterPanelSelector, { visible: true }); + await page.waitForSelector(popoverTrigger); + + const popOverSelector = await getPopoverSelector(await page.$(popoverTrigger)); + const { fromDateSelector, toDateSelector, fromTimeSelector, toTimeSelector } = getPeriodInputsSelectors(popOverSelector); + + await fillInput(page, fromDateSelector, '2022-03-22', ['change']); + await fillInput(page, toDateSelector, '2022-03-22', ['change']); + await fillInput(page, fromTimeSelector, '01:00', ['change']); + await fillInput(page, toTimeSelector, '23:59', ['change']); + await openFilteringPanel(page); + await pressElement(page, popoverTrigger); + await waitForTableLength(page, 3); + }); + it('should successfully apply scheme name filter', async () => { const filterSchemeNameInputField= '.fillingSchemeName-filter input'; await goToPage(page, 'lhc-fill-overview');