diff --git a/lib/domain/dtos/filters/LhcFillsFilterDto.js b/lib/domain/dtos/filters/LhcFillsFilterDto.js index d1d2af5929..9db91e46d1 100644 --- a/lib/domain/dtos/filters/LhcFillsFilterDto.js +++ b/lib/domain/dtos/filters/LhcFillsFilterDto.js @@ -12,10 +12,12 @@ */ const Joi = require('joi'); const { validateRange } = require('../../../utilities/rangeUtils'); +const { validateTimeDuration } = require('../../../utilities/validateTime'); exports.LhcFillsFilterDto = Joi.object({ hasStableBeams: Joi.boolean(), fillNumbers: Joi.string().trim().custom(validateRange).messages({ 'any.invalid': '{{#message}}', }), + beamDuration: validateTimeDuration, }); diff --git a/lib/domain/dtos/filters/NumericalComparisonDto.js b/lib/domain/dtos/filters/NumericalComparisonDto.js index d71a7858e9..f0d5802125 100644 --- a/lib/domain/dtos/filters/NumericalComparisonDto.js +++ b/lib/domain/dtos/filters/NumericalComparisonDto.js @@ -24,3 +24,5 @@ exports.FloatComparisonDto = Joi.object({ operator: Joi.string().valid(...NUMERICAL_COMPARISON_OPERATORS), limit: Joi.number().min(0), }); + +exports.NUMERICAL_COMPARISON_OPERATORS = NUMERICAL_COMPARISON_OPERATORS; diff --git a/lib/public/components/Filters/LhcFillsFilter/beamDurationFilter.js b/lib/public/components/Filters/LhcFillsFilter/beamDurationFilter.js new file mode 100644 index 0000000000..2ef0bbc0af --- /dev/null +++ b/lib/public/components/Filters/LhcFillsFilter/beamDurationFilter.js @@ -0,0 +1,31 @@ +/** + * @license + * Copyright CERN and copyright holders of ALICE Trg. 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-Trg.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 { comparisonOperatorFilter } from '../common/filters/comparisonOperatorFilter.js'; +import { rawTextFilter } from '../common/filters/rawTextFilter.js'; + +/** + * Component to filter LHC-fills by beam duration + * + * @param {TextComparisonFilterModel} beamDurationFilterModel beamDurationFilterModel + * @returns {Component} the text field + */ +export const beamDurationFilter = (beamDurationFilterModel) => { + const durationFilter = rawTextFilter( + beamDurationFilterModel.operandInputModel, + { id: 'beam-duration-filter-operand', classes: ['w-100', 'beam-duration-filter'], placeholder: 'e.g 16:14:15 (HH:MM:SS)' }, + ); + + return comparisonOperatorFilter(durationFilter, beamDurationFilterModel.operatorSelectionModel.current, (value) => + beamDurationFilterModel.operatorSelectionModel.select(value), { id: 'beam-duration-filter-operator' }); +}; diff --git a/lib/public/components/Filters/common/filters/TextComparisonFilterModel.js b/lib/public/components/Filters/common/filters/TextComparisonFilterModel.js new file mode 100644 index 0000000000..b6510f8fae --- /dev/null +++ b/lib/public/components/Filters/common/filters/TextComparisonFilterModel.js @@ -0,0 +1,78 @@ +/** + * @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 { ComparisonSelectionModel } from './ComparisonSelectionModel.js'; +import { FilterModel } from '../FilterModel.js'; +import { RawTextFilterModel } from './RawTextFilterModel.js'; + +/** + * TextComparisonFilterModel + */ +export class TextComparisonFilterModel extends FilterModel { + /** + * Constructor + */ + constructor() { + super(); + + this._operatorSelectionModel = new ComparisonSelectionModel(); + this._operatorSelectionModel.visualChange$.bubbleTo(this._visualChange$); + + this._operandInputModel = new RawTextFilterModel(); + // Unless the filter contains a value don't apply the filters. + this._operatorSelectionModel.observe(() => this._operandInputModel.value ? this.notify() : this._visualChange$.notify()); + this._addSubmodel(this._operandInputModel); + } + + /** + * Return raw text filter model + * + * @return {RawTextFilterModel} operand input model + */ + get operandInputModel() { + return this._operandInputModel; + } + + /** + * Get operator selection model + * + * @return {ComparisonSelectionModel} selection model + */ + get operatorSelectionModel() { + return this._operatorSelectionModel; + } + + /** + * @inheritDoc + */ + reset() { + this._operandInputModel.reset(); + this._operatorSelectionModel.reset(); + } + + /** + * @inheritDoc + */ + get normalized() { + return { + operator: this._operatorSelectionModel.current, + limit: this._operandInputModel.value, + }; + } + + /** + * @inheritDoc + */ + get isEmpty() { + return !this._operandInputModel.value; + } +} diff --git a/lib/public/components/Filters/common/filters/rawTextFilter.js b/lib/public/components/Filters/common/filters/rawTextFilter.js index aa72387767..3cf2078103 100644 --- a/lib/public/components/Filters/common/filters/rawTextFilter.js +++ b/lib/public/components/Filters/common/filters/rawTextFilter.js @@ -23,11 +23,12 @@ import { h } from '/js/src/index.js'; * @return {Component} the filter */ export const rawTextFilter = (filterModel, configuration) => { - const { classes = [], placeholder = '' } = configuration || {}; + const { classes = [], placeholder = '', id } = configuration || {}; return h( 'input', { type: 'text', + id: id, class: classes.join(' '), value: filterModel.value, placeholder: placeholder, diff --git a/lib/public/views/Home/Overview/HomePageModel.js b/lib/public/views/Home/Overview/HomePageModel.js index 40b6cfac85..fca0331fe7 100644 --- a/lib/public/views/Home/Overview/HomePageModel.js +++ b/lib/public/views/Home/Overview/HomePageModel.js @@ -32,7 +32,7 @@ export class HomePageModel extends Observable { this._logsOverviewModel = new LogsOverviewModel(model, true); this._logsOverviewModel.bubbleTo(this); - this._lhcFillsOverviewModel = new LhcFillsOverviewModel(true); + this._lhcFillsOverviewModel = new LhcFillsOverviewModel(model, true); this._lhcFillsOverviewModel.bubbleTo(this); } diff --git a/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js b/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js index 5442ed8bcc..8d0ef13e1e 100644 --- a/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js +++ b/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js @@ -25,6 +25,7 @@ import { formatBeamType } from '../../../utilities/formatting/formatBeamType.js' import { frontLink } from '../../../components/common/navigation/frontLink.js'; import { toggleStableBeamOnlyFilter } from '../../../components/Filters/LhcFillsFilter/stableBeamFilter.js'; import { fillNumberFilter } from '../../../components/Filters/LhcFillsFilter/fillNumberFilter.js'; +import { beamDurationFilter } from '../../../components/Filters/LhcFillsFilter/beamDurationFilter.js'; /** * List of active columns for a lhc fills table @@ -108,6 +109,7 @@ export const lhcFillsActiveColumns = { return '-'; }, + filter: (lhcFillModel) => beamDurationFilter(lhcFillModel.filteringModel.get('beamDuration')), profiles: { lhcFill: true, environment: true, diff --git a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js index 55a417dc66..fae9b894f8 100644 --- a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js +++ b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js @@ -17,6 +17,7 @@ import { StableBeamFilterModel } from '../../../components/Filters/LhcFillsFilte import { RawTextFilterModel } from '../../../components/Filters/common/filters/RawTextFilterModel.js'; import { OverviewPageModel } from '../../../models/OverviewModel.js'; import { addStatisticsToLhcFill } from '../../../services/lhcFill/addStatisticsToLhcFill.js'; +import { TextComparisonFilterModel } from '../../../components/Filters/common/filters/TextComparisonFilterModel.js'; /** * Model for the LHC fills overview page @@ -27,6 +28,7 @@ export class LhcFillsOverviewModel extends OverviewPageModel { /** * Constructor * + * @param {model} model global model * @param {boolean} [stableBeamsOnly=false] if true, overview will load stable beam only */ constructor(stableBeamsOnly = false) { @@ -34,10 +36,11 @@ export class LhcFillsOverviewModel extends OverviewPageModel { this._filteringModel = new FilteringModel({ fillNumbers: new RawTextFilterModel(), + beamDuration: new TextComparisonFilterModel(), hasStableBeams: new StableBeamFilterModel(), }); - this._filteringModel.observe(() => this._applyFilters(true)); + this._filteringModel.observe(() => this._applyFilters()); this._filteringModel.visualChange$.bubbleTo(this); this.reset(false); @@ -61,7 +64,10 @@ export class LhcFillsOverviewModel extends OverviewPageModel { * @inheritDoc */ getRootEndpoint() { - return buildUrl('/api/lhcFills', { filter: this.filteringModel.normalized }); + const params = { + filter: this.filteringModel.normalized, + }; + return buildUrl('/api/lhcFills', params); } /** @@ -83,7 +89,7 @@ export class LhcFillsOverviewModel extends OverviewPageModel { this._filteringModel.reset(); if (fetch) { - this._applyFilters(true); + this._applyFilters(); } } diff --git a/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js b/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js index 360b396968..d270572fbe 100644 --- a/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js +++ b/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js @@ -45,7 +45,7 @@ class GetAllLhcFillsUseCase { const queryBuilder = new QueryBuilder(); if (filter) { - const { hasStableBeams, fillNumbers } = filter; + const { hasStableBeams, fillNumbers, beamDuration } = filter; if (hasStableBeams) { // For now, if a stableBeamsStart is present, then a beam is stable queryBuilder.where('stableBeamsStart').not().is(null); @@ -62,6 +62,10 @@ class GetAllLhcFillsUseCase { : queryBuilder.where('fillNumber').oneOf(...finalFillnumberList); } } + // Beam duration filter, limit and corresponding operator. + if (beamDuration?.limit !== undefined && beamDuration?.operator) { + queryBuilder.where('stableBeamsDuration').applyOperator(beamDuration.operator, beamDuration.limit); + } } const { count, rows } = await TransactionHelper.provide(async () => { diff --git a/lib/utilities/validateTime.js b/lib/utilities/validateTime.js new file mode 100644 index 0000000000..3b9349883b --- /dev/null +++ b/lib/utilities/validateTime.js @@ -0,0 +1,51 @@ +/** + * @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 Joi from 'joi'; +import { NUMERICAL_COMPARISON_OPERATORS } from '../domain/dtos/filters/NumericalComparisonDto.js'; + +const joiTimeDurationErrorText = 'Invalid duration value'; + +/** + * Transform digital time in string format + * + * @param {string} incomingValue The time to transform + * @param {*} helpers The Joi helpers object + * @returns {number|import("joi").ValidationError} The value if transformation passes, as seconds (Number) + */ +export const transformTime = (incomingValue, helpers) => { + try { + // Extract time to seconds... + const [hoursStr, minutesStr, secondsStr] = incomingValue.split(':'); + + const hours = Number(hoursStr); + const minutes = Number(minutesStr); + const seconds = Number(secondsStr); + + return hours * 3600 + minutes * 60 + seconds; + } catch (error) { + return helpers.error('any.invalid', { message: `Validation error: ${error?.message ?? 'failed to transform time'}` }); + } +}; + +/** + * Joi object that validates time duration filters. + * This is for duration, not a point in time. 10000:59:59 is valid. + * The operator is also validated. + */ +export const validateTimeDuration = Joi.object({ + limit: Joi.string().trim().pattern(/^\d+:[0-5]?\d:[0-5]?\d$/).custom(transformTime).messages({ + 'string.pattern.base': joiTimeDurationErrorText, + 'string.base': joiTimeDurationErrorText, + }), + operator: Joi.string().valid(...NUMERICAL_COMPARISON_OPERATORS), +}); diff --git a/test/api/lhcFills.test.js b/test/api/lhcFills.test.js index ab57c3f380..2983e4dd86 100644 --- a/test/api/lhcFills.test.js +++ b/test/api/lhcFills.test.js @@ -35,6 +35,222 @@ module.exports = () => { done(); }); }); + + it('should return 200 and an LHCFill array for stablebeams only filter', (done) => { + request(server) + .get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[hasStableBeams]=true') + .expect(200) + .end((err, res) => { + if (err) { + done(err); + return; + } + + expect(res.body.data).to.have.lengthOf(5); + + done(); + }); + }); + + it('should return 200 and an LHCFill array for stablebeams duration filter, = 12:00:00', (done) => { + request(server) + .get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[beamDuration][operator]==&filter[beamDuration][limit]=12:00:00') + .expect(200) + .end((err, res) => { + if (err) { + done(err); + return; + } + + expect(res.body.data).to.have.lengthOf(1); + expect(res.body.data[0].fillNumber).to.equal(6); + + done(); + }); + }); + + it('should return 200 for stablebeams duration filter, = 00:9:00', (done) => { + request(server) + .get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[beamDuration][operator]==&filter[beamDuration][limit]=00:9:00') + .expect(200) + .end((err, res) => { + if (err) { + done(err); + return; + } + + expect(res.body.data).to.have.lengthOf(0); + + done(); + }); + }); + + it('should return 200 for stablebeams duration filter, = 00:00:9', (done) => { + request(server) + .get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[beamDuration][operator]==&filter[beamDuration][limit]=00:00:9') + .expect(200) + .end((err, res) => { + if (err) { + done(err); + return; + } + + expect(res.body.data).to.have.lengthOf(0); + + done(); + }); + }); + + it('should return 200 for stablebeams duration filter, = 999999:00:00', (done) => { + request(server) + .get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[beamDuration][operator]==&filter[beamDuration][limit]=999999:00:00') + .expect(200) + .end((err, res) => { + if (err) { + done(err); + return; + } + + expect(res.body.data).to.have.lengthOf(0); + + done(); + }); + }); + + + it('should return 200 for stablebeams duration filter, = 999999:0:0', (done) => { + request(server) + .get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[beamDuration][operator]==&filter[beamDuration][limit]=999999:0:0') + .expect(200) + .end((err, res) => { + if (err) { + done(err); + return; + } + + expect(res.body.data).to.have.lengthOf(0); + + done(); + }); + }); + + + + it('should return 400 for wrong stablebeams duration filter, = 44:60:00', (done) => { + request(server) + .get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[beamDuration][operator]==&filter[beamDuration][limit]=44:60:00') + .expect(400) + .end((err, res) => { + if (err) { + done(err); + return; + } + + expect(res.body.errors[0].title).to.equal('Invalid Attribute'); + + done(); + }); + }); + + it('should return 400 for wrong stablebeams duration filter, = 44:00:60', (done) => { + request(server) + .get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[beamDuration][operator]==&filter[beamDuration][limit]=44:00:60') + .expect(400) + .end((err, res) => { + if (err) { + done(err); + return; + } + + expect(res.body.errors[0].title).to.equal('Invalid Attribute'); + + done(); + }); + }); + + it('should return 400 for wrong stablebeams duration filter, = -44:30:15', (done) => { + request(server) + .get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[beamDuration][operator]==&filter[beamDuration][limit]=-44:30:15') + .expect(400) + .end((err, res) => { + if (err) { + done(err); + return; + } + + expect(res.body.errors[0].title).to.equal('Invalid Attribute'); + + done(); + }); + }); + + + it('should return 200 and an LHCFill array for stablebeams duration filter, < 12:00:00', (done) => { + request(server) + .get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[beamDuration][operator]=<&filter[beamDuration][limit]=12:00:00') + .expect(200) + .end((err, res) => { + if (err) { + done(err); + return; + } + + expect(res.body.data).to.have.lengthOf(3); + expect(res.body.data[0].fillNumber).to.equal(3); + + done(); + }); + }); + + it('should return 200 and an LHCFill array for stablebeams duration filter, <= 12:00:00', (done) => { + request(server) + .get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[beamDuration][operator]=<=&filter[beamDuration][limit]=12:00:00') + .expect(200) + .end((err, res) => { + if (err) { + done(err); + return; + } + + expect(res.body.data).to.have.lengthOf(4); + expect(res.body.data[0].fillNumber).to.equal(6); + + done(); + }); + }); + + it('should return 200 and an LHCFill array for stablebeams duration filter, >= 12:00:00', (done) => { + request(server) + .get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[beamDuration][operator]=>=&filter[beamDuration][limit]=12:00:00') + .expect(200) + .end((err, res) => { + if (err) { + done(err); + return; + } + + expect(res.body.data).to.have.lengthOf(1); + expect(res.body.data[0].fillNumber).to.equal(6); + + done(); + }); + }); + + it('should return 200 and an LHCFill array for stablebeams duration filter, > 12:00:00', (done) => { + request(server) + .get('/api/lhcFills?page[offset]=0&page[limit]=15&filter[beamDuration][operator]=>&filter[beamDuration][limit]=12:00:00') + .expect(200) + .end((err, res) => { + if (err) { + done(err); + return; + } + + expect(res.body.data).to.have.lengthOf(0); + + done(); + }); + }); }); describe('POST /api/lhcFills', () => { it('should return 201 if valid data is provided', async () => { diff --git a/test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js b/test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js index fdccf49678..0d35c81e05 100644 --- a/test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js +++ b/test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js @@ -100,4 +100,60 @@ module.exports = () => { expect(lhcFill.fillNumber).oneOf([6,3]) }); }) + + // Beam duration filter tests + + it('should only contain specified stable beam durations, < 12:00:00', async () => { + getAllLhcFillsDto.query = { filter: { beamDuration: {limit: '43200', operator: '<'} } }; + const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto); + expect(lhcFills).to.be.an('array').and.lengthOf(3) + lhcFills.forEach((lhcFill) => { + expect(lhcFill.stableBeamsDuration).lessThan(43200) + }); + }); + + it('should only contain specified stable beam durations, <= 12:00:00', async () => { + getAllLhcFillsDto.query = { filter: { beamDuration: {limit: '43200', operator: '<='} } }; + const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto) + expect(lhcFills).to.be.an('array').and.lengthOf(4) + lhcFills.forEach((lhcFill) => { + expect(lhcFill.stableBeamsDuration).lessThanOrEqual(43200) + }); + }) + + it('should only contain specified stable beam durations, = 00:01:40', async () => { + getAllLhcFillsDto.query = { filter: { beamDuration: {limit: '100', operator: '='} } }; + const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto) + expect(lhcFills).to.be.an('array').and.lengthOf(3) + lhcFills.forEach((lhcFill) => { + expect(lhcFill.stableBeamsDuration).equal(100) + }); + }); + + it('should only contain specified stable beam durations, >= 00:01:40', async () => { + getAllLhcFillsDto.query = { filter: { beamDuration: {limit: '100', operator: '>='} } }; + const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto) + + expect(lhcFills).to.be.an('array').and.lengthOf(4) + lhcFills.forEach((lhcFill) => { + expect(lhcFill.stableBeamsDuration).greaterThanOrEqual(100) + }); + }) + + it('should only contain specified stable beam durations, > 00:01:40', async () => { + getAllLhcFillsDto.query = { filter: { beamDuration: {limit: '100', operator: '>'} } }; + const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto) + + expect(lhcFills).to.be.an('array').and.lengthOf(1) + lhcFills.forEach((lhcFill) => { + expect(lhcFill.stableBeamsDuration).greaterThan(100) + }); + }) + + it('should only contain specified stable beam durations, = 00:00:00', async () => { + getAllLhcFillsDto.query = { filter: { hasStableBeams: true, beamDuration: {limit: '0', operator: '='} } }; + const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto) + + expect(lhcFills).to.be.an('array').and.lengthOf(0) + }) }; diff --git a/test/public/lhcFills/overview.test.js b/test/public/lhcFills/overview.test.js index 02b05c1591..83ad6d996c 100644 --- a/test/public/lhcFills/overview.test.js +++ b/test/public/lhcFills/overview.test.js @@ -24,6 +24,8 @@ const { waitForTableLength, expectLink, openFilteringPanel, + expectAttributeValue, + fillInput, } = require('../defaults.js'); const { resetDatabaseContent } = require('../../utilities/resetDatabaseContent.js'); @@ -267,12 +269,21 @@ 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 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 filterSBDurationPlaceholderExpect = {selector: 'input.w-100:nth-child(2)', value: 'e.g 16:14:15 (HH:MM:SS)'}; + const filterSBDurationOperatorExpect = { value: true }; + + await goToPage(page, 'lhc-fill-overview'); // Open the filtering panel await openFilteringPanel(page); + // Note: expectAttributeValue does not work here. + 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, filterSBDurationExpect.selector, filterSBDurationExpect.value); + await expectAttributeValue(page, filterSBDurationPlaceholderExpect.selector, 'placeholder', filterSBDurationPlaceholderExpect.value); }); it('should successfully un-apply Stable Beam filter menu', async () => { @@ -291,4 +302,16 @@ module.exports = () => { await pressElement(page, '.slider.round'); await waitForTableLength(page, 6); }); + + it('should successfully apply beam duration filter', async () => { + const filterSBDurationOperator= '#beam-duration-filter-operator'; + const filterSBDurationOperand= '#beam-duration-filter-operand'; + await goToPage(page, 'lhc-fill-overview'); + await waitForTableLength(page, 5); + // Open the filtering panel + await openFilteringPanel(page); + await page.select(filterSBDurationOperator, '>='); + await fillInput(page, filterSBDurationOperand, '00:01:40', ['change']); + await waitForTableLength(page, 4); + }); };