diff --git a/lib/database/repositories/LhcFillRepository.js b/lib/database/repositories/LhcFillRepository.js index 8ed50d16cb..c74f9ffdd4 100644 --- a/lib/database/repositories/LhcFillRepository.js +++ b/lib/database/repositories/LhcFillRepository.js @@ -34,6 +34,10 @@ const getFillNumbersWithStableBeamsEndedInPeriodQuery = (period) => ` AND stable_beams_start IS NOT NULL `; +const getLhcFillDistinctBeamTypesQuery = () => ` + SELECT DISTINCT beam_type FROM bookkeeping.lhc_fills +`; + /** * Sequelize implementation of the RunRepository. */ @@ -45,6 +49,14 @@ class LhcFillRepository extends Repository { super(LhcFill); } + /** + * Return the list of LHC fills distict beam types. + * @returns {Promise} + */ + async getLhcFillDistinctBeamTypes() { + return await sequelize.query(getLhcFillDistinctBeamTypesQuery(), { type: QueryTypes.SELECT, raw: true }); + } + /** * Return the list of LHC fills numbers for fills with stable beams that ended in the given period * diff --git a/lib/database/utilities/QueryBuilder.js b/lib/database/utilities/QueryBuilder.js index 2f522902e8..ab16d35ccf 100644 --- a/lib/database/utilities/QueryBuilder.js +++ b/lib/database/utilities/QueryBuilder.js @@ -88,7 +88,7 @@ class WhereQueryBuilder { } /** - * Sets an **OR** match filter using the provided values. + * Sets an **IN** match filter using the provided values. * * @param {...any} values The required values. * @returns {QueryBuilder} The current QueryBuilder instance. @@ -104,6 +104,39 @@ class WhereQueryBuilder { return this._op(operation); } + /** + * Sets an **OR** match filter using the provided values. + * Adds an **OR NULL** filter. + * If the spread returns empty array the filter becomes an **IS NULL** filter (**OR** is not valid anymore) + * + * @param {...any} values The required values. + * @returns {QueryBuilder} The current QueryBuilder instance. + */ + oneOfOrNull(...values) { + let operation; + if (this.notFlag) { + operation = values[0]?.length === 0 ? { + [Op.not]: null, + } : { + [Op.or]: { + [Op.notIn]: values, + [Op.not]: null, + }, + }; + } else { + operation = values[0]?.length === 0 ? { + [Op.is]: null, + } : { + [Op.or]: { + [Op.in]: values, + [Op.is]: null, + }, + }; + } + + return this._op(operation); + } + /** * Set a max range limit using the provided value * diff --git a/lib/domain/dtos/GetAllBeamsTypesDto.js b/lib/domain/dtos/GetAllBeamsTypesDto.js new file mode 100644 index 0000000000..c4e78bfec5 --- /dev/null +++ b/lib/domain/dtos/GetAllBeamsTypesDto.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. + */ + +const Joi = require('joi'); +const PaginationDto = require('./PaginationDto'); + +const QueryDto = Joi.object({ + page: PaginationDto, + token: Joi.string(), +}); + +const GetAllBeamsTypesDto = Joi.object({ + body: Joi.object({}), + params: Joi.object({}), + query: QueryDto, +}); + +module.exports = GetAllBeamsTypesDto; diff --git a/lib/domain/dtos/filters/LhcFillsFilterDto.js b/lib/domain/dtos/filters/LhcFillsFilterDto.js index 7c5d6853f7..2942844758 100644 --- a/lib/domain/dtos/filters/LhcFillsFilterDto.js +++ b/lib/domain/dtos/filters/LhcFillsFilterDto.js @@ -27,4 +27,5 @@ exports.LhcFillsFilterDto = Joi.object({ 'any.invalid': '{{#message}}', }), runDurationOperator: Joi.string().trim().min(1).max(2), + beamsType: Joi.string(), }); diff --git a/lib/domain/dtos/index.js b/lib/domain/dtos/index.js index 01eb348934..d1bb88492f 100644 --- a/lib/domain/dtos/index.js +++ b/lib/domain/dtos/index.js @@ -18,6 +18,7 @@ const CreateLhcFillDto = require('./CreateLhcFillDto'); const CreateLogDto = require('./CreateLogDto'); const CreateTagDto = require('./CreateTagDto'); const EntityIdDto = require('./EntityIdDto'); +const GetAllBeamsTypesDto = require('./GetAllBeamsTypesDto.js'); const GetAllEnvironmentsDto = require('./GetAllEnvironmentsDto'); const GetAllLhcFillsDto = require('./GetAllLhcFillsDto'); const GetAllLogAttachmentsDto = require('./GetAllLogAttachmentsDto'); @@ -56,6 +57,7 @@ module.exports = { CreateTagDto, EndRunDto, EntityIdDto, + GetAllBeamsTypesDto, GetAllEnvironmentsDto, GetAllLhcFillsDto, GetAllLogAttachmentsDto, diff --git a/lib/public/components/Filters/LhcFillsFilter/BeamsTypeFilterModel.js b/lib/public/components/Filters/LhcFillsFilter/BeamsTypeFilterModel.js new file mode 100644 index 0000000000..389328b220 --- /dev/null +++ b/lib/public/components/Filters/LhcFillsFilter/BeamsTypeFilterModel.js @@ -0,0 +1,59 @@ +/** + * @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 { beamsTypesProvider } from '../../../services/beamsTypes/beamsTypesProvider.js'; +import { SelectionModel } from '../../common/selection/SelectionModel.js'; + +/** + * Beam type filter model + */ +export class BeamsTypeFilterModel extends SelectionModel { + /** + * Constructor + */ + constructor() { + let beamTypes = []; + super({ availableOptions: beamTypes, + defaultSelection: [], + multiple: true, + allowEmpty: true }); + + beamsTypesProvider.items$.observe(() => { + beamsTypesProvider.items$.getCurrent().apply({ + Success: (types) => { + beamTypes = types.map((type) => ({ value: String(type.beam_type) })); + this.setAvailableOptions(beamTypes); + }, + }); + }); + } + + /** + * Get normalized selected option + */ + get normalized() { + return this.selected.join(','); + } + + /** + * Reset the filter to default values + * + * @return {void} + */ + resetDefaults() { + if (!this.isEmpty) { + this.reset(); + this.notify(); + } + } +} diff --git a/lib/public/components/Filters/LhcFillsFilter/beamsTypeFilter.js b/lib/public/components/Filters/LhcFillsFilter/beamsTypeFilter.js new file mode 100644 index 0000000000..94a5887d42 --- /dev/null +++ b/lib/public/components/Filters/LhcFillsFilter/beamsTypeFilter.js @@ -0,0 +1,26 @@ +/** + * @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 { checkboxes } from '../common/filters/checkboxFilter.js'; + +/** + * Renders a list of checkboxes that lets the user look for beam types + * + * @param {BeamsTypeFilterModel} beamsTypeFilterModel beamsTypeFilterModel + * @return {Component} the filter + */ +export const beamsTypeFilter = (beamsTypeFilterModel) => + checkboxes( + beamsTypeFilterModel, + { selector: 'beams-types' }, + ); diff --git a/lib/public/services/beamsTypes/beamsTypesProvider.js b/lib/public/services/beamsTypes/beamsTypesProvider.js new file mode 100644 index 0000000000..20a688d0a2 --- /dev/null +++ b/lib/public/services/beamsTypes/beamsTypesProvider.js @@ -0,0 +1,30 @@ +/** + * @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 { getRemoteData } from '../../utilities/fetch/getRemoteData.js'; +import { RemoteDataProvider } from '../RemoteDataProvider.js'; + +/** + * Service class to fetch beams types from the backend + */ +export class BeamsTypesProvider extends RemoteDataProvider { + /** + * @inheritDoc + */ + async getRemoteData() { + const { data } = await getRemoteData('/api/beamsTypes'); + return data; + } +} + +export const beamsTypesProvider = new BeamsTypesProvider(); diff --git a/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js b/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js index 1a0cd8aa9d..897b407d13 100644 --- a/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js +++ b/lib/public/views/LhcFills/ActiveColumns/lhcFillsActiveColumns.js @@ -27,6 +27,7 @@ import { toggleStableBeamOnlyFilter } from '../../../components/Filters/LhcFills import { fillNumberFilter } from '../../../components/Filters/LhcFillsFilter/fillNumberFilter.js'; import { beamDurationFilter } from '../../../components/Filters/LhcFillsFilter/beamDurationFilter.js'; import { runDurationFilter } from '../../../components/Filters/LhcFillsFilter/runDurationFilter.js'; +import { beamsTypeFilter } from '../../../components/Filters/LhcFillsFilter/beamsTypeFilter.js'; /** * List of active columns for a lhc fills table @@ -170,6 +171,7 @@ export const lhcFillsActiveColumns = { visible: true, size: 'w-8', format: (value) => formatBeamType(value), + filter: (lhcFillModel) => beamsTypeFilter(lhcFillModel.filteringModel.get('beamsType')), }, collidingBunches: { name: 'Colliding bunches', diff --git a/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js b/lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js index 9837d58473..5689428c96 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 { BeamsTypeFilterModel } from '../../../components/Filters/LhcFillsFilter/BeamsTypeFilterModel.js'; const defaultBeamDurationOperator = '='; const defaultRunDurationOperator = '='; @@ -35,11 +36,14 @@ export class LhcFillsOverviewModel extends OverviewPageModel { constructor(stableBeamsOnly = false) { super(); + this._beamsTypes = []; + this._filteringModel = new FilteringModel({ fillNumbers: new RawTextFilterModel(), beamDuration: new RawTextFilterModel(), runDuration: new RawTextFilterModel(), hasStableBeams: new StableBeamFilterModel(), + beamsType: new BeamsTypeFilterModel(), }); this._beamDurationOperator = defaultBeamDurationOperator; @@ -81,6 +85,13 @@ export class LhcFillsOverviewModel extends OverviewPageModel { return buildUrl('/api/lhcFills', params); } + /** + * Getter + */ + getBeamsTypes() { + return this._beamsTypes; + } + /** * Setter function for runDurationOperator */ diff --git a/lib/server/controllers/beamsTypes.controller.js b/lib/server/controllers/beamsTypes.controller.js new file mode 100644 index 0000000000..e67b21718c --- /dev/null +++ b/lib/server/controllers/beamsTypes.controller.js @@ -0,0 +1,62 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * 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. + */ + +const { + beamsType: { + GetAllBeamsTypesUseCase, + }, +} = require('../../usecases/index.js'); +const { + dtos: { + GetAllBeamsTypesDto, + }, +} = require('../../domain/index.js'); +const { dtoValidator } = require('../utilities/index.js'); +const { ApiConfig } = require('../../config/index.js'); + +/** + * Get all beams types. + * + * @param {Object} request The *request* object represents the HTTP request and has properties for the request query + * string, parameters, body, HTTP headers, and so on. + * @param {Object} response The *response* object represents the HTTP response that an Express app sends when it gets an + * HTTP request. + * @returns {undefined} + */ +const listBeamsTypes = async (request, response) => { + const value = await dtoValidator(GetAllBeamsTypesDto, request, response); + if (!value) { + return; + } + + const { count, beamsTypes } = await new GetAllBeamsTypesUseCase() + .execute(value); + + const { query: { page: { limit = ApiConfig.pagination.limit } = {} } } = value; + const totalPages = Math.ceil(count / limit); + + response.status(200).json({ + data: beamsTypes, + meta: { + page: { + pageCount: totalPages, + totalCount: count, + }, + }, + }); +}; + +module.exports = { + listBeamsTypes, +}; diff --git a/lib/server/controllers/index.js b/lib/server/controllers/index.js index 48184378b2..904dadaeb1 100644 --- a/lib/server/controllers/index.js +++ b/lib/server/controllers/index.js @@ -12,6 +12,7 @@ */ const AttachmentsController = require('./attachments.controller'); +const BeamsTypeController = require('./beamsTypes.controller.js'); const ConfigurationController = require('./configuration.controller.js'); const DetectorsController = require('./detectors.controller'); const EnvironmentsController = require('./environments.controller'); @@ -26,6 +27,7 @@ const CtpTriggerCountersController = require('./ctpTriggerCounters.controller'); module.exports = { AttachmentsController, + BeamsTypeController, ConfigurationController, DetectorsController, EnvironmentsController, diff --git a/lib/server/routers/beamsTypes.router.js b/lib/server/routers/beamsTypes.router.js new file mode 100644 index 0000000000..a64aa6b4f8 --- /dev/null +++ b/lib/server/routers/beamsTypes.router.js @@ -0,0 +1,20 @@ +/** + * @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. + */ + +const { BeamsTypeController } = require('../controllers'); + +exports.beamsTypesRouter = { + path: '/beamsTypes', + controller: BeamsTypeController.listBeamsTypes, + method: 'get', +}; diff --git a/lib/server/routers/index.js b/lib/server/routers/index.js index cb83465446..a064f5a246 100644 --- a/lib/server/routers/index.js +++ b/lib/server/routers/index.js @@ -15,6 +15,7 @@ const { deepmerge, isPromise } = require('../../utilities'); const attachmentRoute = require('./attachments.router'); const { configurationRouter } = require('./configuration.router.js'); +const { beamsTypesRouter } = require('./beamsTypes.router.js') const detectorsRoute = require('./detectors.router'); const { dplProcessRouter } = require('./dplProcess.router.js'); const environmentRoute = require('./environments.router'); @@ -40,6 +41,7 @@ const { ctpTriggerCountersRouter } = require('./ctpTriggerCounters.router.js'); const routes = [ attachmentRoute, + beamsTypesRouter, configurationRouter, detectorsRoute, dataPassesRouter, diff --git a/lib/usecases/beamsType/GetAllBeamsTypesUseCase.js b/lib/usecases/beamsType/GetAllBeamsTypesUseCase.js new file mode 100644 index 0000000000..405e9b5f14 --- /dev/null +++ b/lib/usecases/beamsType/GetAllBeamsTypesUseCase.js @@ -0,0 +1,34 @@ +/** + * @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. + */ + +const LhcFillRepository = require('../../database/repositories/LhcFillRepository.js'); + +/** + * GetAllBeamsTypesUseCase + */ +class GetAllBeamsTypesUseCase { + /** + * Executes this use case. + * + * @returns {Promise} Promise object represents the result of this use case. + */ + async execute() { + const result = await LhcFillRepository.getLhcFillDistinctBeamTypes(); + return { + count: result.length, + beamsTypes: result, + }; + } +} + +module.exports = GetAllBeamsTypesUseCase; diff --git a/lib/usecases/beamsType/index.js b/lib/usecases/beamsType/index.js new file mode 100644 index 0000000000..5a60a1e65f --- /dev/null +++ b/lib/usecases/beamsType/index.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. + */ +const GetAllBeamsTypesUseCase = require('./GetAllBeamsTypesUseCase.js'); + +module.exports = { + GetAllBeamsTypesUseCase, +}; diff --git a/lib/usecases/index.js b/lib/usecases/index.js index 2dfa7807e5..413ffe4791 100644 --- a/lib/usecases/index.js +++ b/lib/usecases/index.js @@ -12,6 +12,7 @@ */ const attachment = require('./attachment'); +const beamsType = require('./beamsType'); const environment = require('./environment'); const flp = require('./flp'); const lhcFill = require('./lhcFill'); @@ -24,6 +25,7 @@ const tag = require('./tag'); module.exports = { attachment, + beamsType, environment, flp, lhcFill, diff --git a/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js b/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js index e961548817..958f07cbe2 100644 --- a/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js +++ b/lib/usecases/lhcFill/GetAllLhcFillsUseCase.js @@ -47,7 +47,7 @@ class GetAllLhcFillsUseCase { let associatedStatisticsRequired = false; if (filter) { - const { hasStableBeams, fillNumbers, beamDurationOperator, beamDuration, runDurationOperator, runDuration } = filter; + const { hasStableBeams, fillNumbers, beamDurationOperator, beamDuration, runDurationOperator, runDuration, beamsType } = filter; if (hasStableBeams) { // For now, if a stableBeamsStart is present, then a beam is stable queryBuilder.where('stableBeamsStart').not().is(null); @@ -77,6 +77,18 @@ class GetAllLhcFillsUseCase { beamDuration === 0 ? queryBuilder.where('stableBeamsDuration').applyOperator(beamDurationOperator, null) : queryBuilder.where('stableBeamsDuration').applyOperator(beamDurationOperator, beamDuration); } + + // Beams type. + if (beamsType) { + let beamTypes = beamsType.split(','); + // Check if 'null' is included in the request + if (beamTypes.find((type) => type.trim() === 'null') !== undefined) { + beamTypes = beamTypes.filter((type) => type.trim() !== 'null'); + queryBuilder.where('beamType').oneOfOrNull(beamTypes); + } else { + queryBuilder.where('beamType').oneOf(beamTypes); + } + } } const { count, rows } = await TransactionHelper.provide(async () => { diff --git a/test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js b/test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js index 6c317588ff..c9284001f5 100644 --- a/test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js +++ b/test/lib/usecases/lhcFill/GetAllLhcFillsUseCase.test.js @@ -220,4 +220,44 @@ module.exports = () => { expect(lhcFill.statistics.runsCoverage).greaterThan(23459) }); }) + + it('should only contain specified beam types, {p-p, PROTON-PROTON, Pb-Pb}', async () => { + const beamTypes = ['p-p', ' PROTON-PROTON', 'Pb-Pb'] + + getAllLhcFillsDto.query = { filter: { beamsType: beamTypes.join(',') } }; + const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto) + + expect(lhcFills).to.be.an('array').and.lengthOf(4) + lhcFills.forEach((lhcFill) => { + expect(lhcFill.beamType).oneOf(beamTypes) + }); + }) + + it('should only contain specified beam types, OR NULL, {p-p, PROTON-PROTON, Pb-Pb, null}', async () => { + let beamTypes = ['p-p', ' PROTON-PROTON', 'Pb-Pb', 'null'] + + getAllLhcFillsDto.query = { filter: { beamsType: beamTypes.join(',') } }; + const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto) + + expect(lhcFills).to.be.an('array').and.lengthOf(5) + + const nullIndex = beamTypes.findIndex((value) => value ==='null') + beamTypes[nullIndex] = null; + + lhcFills.forEach((lhcFill) => { + expect(lhcFill.beamType).oneOf(beamTypes) + }); + }) + + it('should only contain specified beam type, IS NULL, {null}', async () => { + const beamTypes = ['null'] + + getAllLhcFillsDto.query = { filter: { beamsType: beamTypes.join(',') } }; + const { lhcFills } = await new GetAllLhcFillsUseCase().execute(getAllLhcFillsDto) + + expect(lhcFills).to.be.an('array').and.lengthOf(1) + lhcFills.forEach((lhcFill) => { + expect(lhcFill.beamType).oneOf([null]) + }); + }) }; diff --git a/test/public/lhcFills/overview.test.js b/test/public/lhcFills/overview.test.js index c67a1ebb3c..adea7f85fa 100644 --- a/test/public/lhcFills/overview.test.js +++ b/test/public/lhcFills/overview.test.js @@ -273,6 +273,7 @@ module.exports = () => { const filterSBDurationPlaceholderExpect = {selector: '.beam-duration-filter', 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 filterRunDurationPlaceholderExpect = {selector: '.run-duration-filter', value: 'e.g 16:14:15 (HH:MM:SS)'} + const filterBeamTypeExpect = {selector: 'div.flex-row:nth-child(5) > div:nth-child(1)', value: 'Beam Type'} await goToPage(page, 'lhc-fill-overview'); @@ -284,6 +285,7 @@ module.exports = () => { await expectAttributeValue(page, filterSBDurationPlaceholderExpect.selector, 'placeholder', filterSBDurationPlaceholderExpect.value); await expectInnerText(page, filterRunDurationExpect.selector, filterRunDurationExpect.value); await expectAttributeValue(page, filterRunDurationPlaceholderExpect.selector, 'placeholder', filterRunDurationPlaceholderExpect.value); + await expectInnerText(page, filterBeamTypeExpect.selector, filterBeamTypeExpect.value); }); it('should successfully un-apply Stable Beam filter menu', async () => {