diff --git a/lib/public/Model.js b/lib/public/Model.js index 2d98c562ed..8cb3d70700 100644 --- a/lib/public/Model.js +++ b/lib/public/Model.js @@ -33,6 +33,7 @@ import { HomePageModel } from './views/Home/Overview/HomePageModel.js'; import { DataPassesModel } from './views/DataPasses/DataPassesModel.js'; import { SimulationPassesModel } from './views/SimulationPasses/SimulationPassesModel.js'; import { QcFlagTypesModel } from './views/QcFlagTypes/QcFlagTypesModel.js'; +import { QcFlagsModel } from './views/QcFlags/QcFlagsModel.js'; /** * Root of model tree @@ -79,6 +80,9 @@ export default class Model extends Observable { this.dataPasses = new DataPassesModel(this); this.dataPasses.bubbleTo(this); + this.qcFlags = new QcFlagsModel(); + this.qcFlags.bubbleTo(this); + this.simulationPasses = new SimulationPassesModel(this); this.simulationPasses.bubbleTo(this); @@ -187,6 +191,12 @@ export default class Model extends Observable { case 'qc-flag-type-creation': this.qcFlagTypes.loadCreation(); break; + case 'qc-flags-for-data-pass': + this.qcFlags.loadForDataPassOverview(this.router.params); + break; + case 'qc-flags-for-simulation-pass': + this.qcFlags.loadForSimulationPassOverview(this.router.params); + break; case 'anchored-simulation-passes-overview': this.simulationPasses.loadAnchoredOverview(this.router.params); break; diff --git a/lib/public/services/detectors/dplDetectorsProvider.js b/lib/public/services/detectors/dplDetectorsProvider.js new file mode 100644 index 0000000000..cdf5ee6935 --- /dev/null +++ b/lib/public/services/detectors/dplDetectorsProvider.js @@ -0,0 +1,90 @@ +/** + * @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 { Observable, RemoteData } from '/js/src/index.js'; +import { getRemoteData } from '../../utilities/fetch/getRemoteData.js'; +import { ObservableData } from '../../utilities/ObservableData.js'; + +const TST_DETECTOR_NAME = 'TST'; + +/** + * Service class to fetch DPL detectors from the backend + */ +export class DplDetectorsProvider extends Observable { + /** + * Constructor + */ + constructor() { + super(); + this._all$ = new ObservableData(RemoteData.notAsked()); + this._physical$ = ObservableData.builder() + .source(this._all$) + .apply((remoteDetectors) => remoteDetectors.apply({ + Success: (detectors) => detectors.filter(({ name }) => name && name !== TST_DETECTOR_NAME), + })) + .build(); + } + + /** + * Return all DPL detectors observable data + * + * @return {ObservableData>} the observable DPL detectors list + */ + get all$() { + if (this._isStale()) { + this._load(); + } + return this._all$; + } + + /** + * Return physical (meaning actual detectors, for example excluding TST) DPL detectors list observable data + * + * @return {ObservableData>} the observable physical DPL detectors list + */ + get physical$() { + if (this._isStale()) { + this._load(); + } + return this._physical$; + } + + /** + * States if the cached detectors list need to be created or updated + * + * @return {boolean} true if the detectors list must be refreshed + * @private + */ + _isStale() { + return this._all$.getCurrent().match({ + NotAsked: () => true, + Other: () => false, + }); + } + + /** + * Load all the detectors and feed the observable data + * + * @return {void} + * @private + */ + _load() { + this._all$.setCurrent(RemoteData.loading()); + getRemoteData('/api/dpl-detectors').then( + ({ data: detectors }) => this._all$.setCurrent(RemoteData.success(detectors)), + (error) => this._all$.setCurrent(RemoteData.failure(error)), + ); + } +} + +export const dplDetectorsProvider = new DplDetectorsProvider(); diff --git a/lib/public/view.js b/lib/public/view.js index 9e50d8593a..3cb9624462 100644 --- a/lib/public/view.js +++ b/lib/public/view.js @@ -47,6 +47,8 @@ import { LogReplyPage } from './views/Logs/Create/LogReplyPage.js'; import { QcFlagTypeCreationPage } from './views/QcFlagTypes/Create/QcFlagTypeCreationPage.js'; import { RunsPerSimulationPassOverviewPage } from './views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewPage.js'; import { QcFlagTypesOverviewPage } from './views/QcFlagTypes/Overview/QcFlagTypesOverviewPage.js'; +import { QcFlagsForDataPassOverviewPage } from './views/QcFlags/ForDataPass/QcFlagsForDataPassOverviewPage.js'; +import { QcFlagsForSimulationPassOverviewPage } from './views/QcFlags/ForSimulationPass/QcFlagsForSimulationPassOverviewPage.js'; /** * Main view layout @@ -84,6 +86,9 @@ export default (model) => { 'qc-flag-type-creation': QcFlagTypeCreationPage, 'qc-flag-types-overview': QcFlagTypesOverviewPage, + 'qc-flags-for-data-pass': QcFlagsForDataPassOverviewPage, + 'qc-flags-for-simulation-pass': QcFlagsForSimulationPassOverviewPage, + statistics: StatisticsPage, 'flp-overview': FlpOverview, diff --git a/lib/public/views/Logs/Create/templates/rcDailyMeetingConfiguration.js b/lib/public/views/Logs/Create/templates/rcDailyMeetingConfiguration.js index 6f85cd51cf..f883ab3dcd 100644 --- a/lib/public/views/Logs/Create/templates/rcDailyMeetingConfiguration.js +++ b/lib/public/views/Logs/Create/templates/rcDailyMeetingConfiguration.js @@ -24,8 +24,8 @@ import { } from '../../../../components/common/form/magnetsConfiguration/aliceMagnetsConfigurationsSnapshotsForm.js'; import { table } from '../../../../components/common/table/table.js'; import { runsActiveColumns } from '../../../Runs/ActiveColumns/runsActiveColumns.js'; -import { createRunDetectorsActiveColumns } from '../../../Runs/ActiveColumns/runDetectorsActiveColumns.js'; import { formatTimeAndEfficiencyLoss } from '../../../../utilities/formatting/formatTimeAndEfficiencyLoss.js'; +import { createRunDetectorsSyncQcActiveColumns } from '../../../Runs/ActiveColumns/runDetectorsSyncQcActiveColumns.js'; /** * On call template configuration @@ -115,7 +115,7 @@ export const rcDailyMeetingConfiguration = (creationModel, template) => { runsToCheck, { ...runsActiveColumns, - ...createRunDetectorsActiveColumns( + ...createRunDetectorsSyncQcActiveColumns( template.physicalDetectors.match({ Success: (payload) => payload, Other: () => [], diff --git a/lib/public/views/QcFlags/ActiveColumns/qcFlagsActiveColumns.js b/lib/public/views/QcFlags/ActiveColumns/qcFlagsActiveColumns.js new file mode 100644 index 0000000000..c1560f772e --- /dev/null +++ b/lib/public/views/QcFlags/ActiveColumns/qcFlagsActiveColumns.js @@ -0,0 +1,71 @@ +/** + * @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 { h } from '/js/src/index.js'; +import { formatTimestamp } from '../../../utilities/formatting/formatTimestamp.js'; +import { qcFlagTypeColoredBadge } from '../common/qcFlagTypeColoredBadge.js'; + +/** + * Active columns configuration for QC flags table + */ +export const qcFlagsActiveColumns = { + id: { + name: 'Id', + visible: false, + }, + + flagType: { + name: 'Type', + visible: true, + format: (flagType) => h('.flex-row.g1', [flagType.name, qcFlagTypeColoredBadge(flagType)]), + }, + + from: { + name: 'From', + visible: true, + format: (timestamp) => timestamp + ? formatTimestamp(timestamp, false) + : 'Whole run coverage', + }, + + to: { + name: 'To', + visible: true, + format: (timestamp) => timestamp + ? formatTimestamp(timestamp, false) + : 'Whole run coverage', + }, + + comment: { + name: 'Comment', + visible: true, + }, + + createdBy: { + name: 'Created by', + visible: true, + format: (createdBy) => createdBy?.name || '-', + }, + + createdAt: { + name: 'Created at', + visible: true, + format: (timestamp) => formatTimestamp(timestamp, false), + }, + + updatedAt: { + name: 'Updated at', + visible: true, + format: (timestamp) => formatTimestamp(timestamp, false), + }, +}; diff --git a/lib/public/views/QcFlags/ForDataPass/QcFlagsForDataPassOverviewModel.js b/lib/public/views/QcFlags/ForDataPass/QcFlagsForDataPassOverviewModel.js new file mode 100644 index 0000000000..c69358532c --- /dev/null +++ b/lib/public/views/QcFlags/ForDataPass/QcFlagsForDataPassOverviewModel.js @@ -0,0 +1,86 @@ +/** + * @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 { buildUrl } from '../../../utilities/fetch/buildUrl.js'; +import { RemoteData } from '/js/src/index.js'; +import { getRemoteData } from '../../../utilities/fetch/getRemoteData.js'; +import { QcFlagsOverviewModel } from '../Overview/QcFlagsOverviewModel.js'; +import { ObservableData } from '../../../utilities/ObservableData.js'; + +/** + * Quality Control Flags for data pass overview model + * + * @implements {OverviewModel} + */ +export class QcFlagsForDataPassOverviewModel extends QcFlagsOverviewModel { + /** + * The constructor of the Overview model object + */ + constructor() { + super(); + this._dataPass$ = new ObservableData(RemoteData.notAsked()); + this._dataPass$.bubbleTo(this); + } + + // eslint-disable-next-line valid-jsdoc + /** + * @inheritdoc + */ + getRootEndpoint() { + const params = { + filter: { + dataPassIds: [this._dataPassId], + }, + }; + return buildUrl(super.getRootEndpoint(), params); + } + + // eslint-disable-next-line valid-jsdoc + /** + * @inheritdoc + */ + load() { + this._fetchDataPass(); + super.load(); + } + + /** + * Fetch data pass data which QC glags are fetched + * @return {Promise} promise + */ + async _fetchDataPass() { + this._dataPass$.setCurrent(RemoteData.loading()); + try { + const { data: [dataPass] } = await getRemoteData(`/api/dataPasses/?filter[ids][]=${this._dataPassId}`); + this._dataPass$.setCurrent(RemoteData.success(dataPass)); + } catch (error) { + this._dataPass$.setCurrent(RemoteData.failure(error)); + } + } + + /** + * Set id of data pass which for QC flags should be fetched + * @param {number} dataPassId data pass id + */ + set dataPassId(dataPassId) { + this._dataPassId = dataPassId; + } + + /** + * Get current data pass which QC flags are fetched + * @return {RemoteData} data pass remote data + */ + get dataPass() { + return this._dataPass$.getCurrent(); + } +} diff --git a/lib/public/views/QcFlags/ForDataPass/QcFlagsForDataPassOverviewPage.js b/lib/public/views/QcFlags/ForDataPass/QcFlagsForDataPassOverviewPage.js new file mode 100644 index 0000000000..ab7c214e5f --- /dev/null +++ b/lib/public/views/QcFlags/ForDataPass/QcFlagsForDataPassOverviewPage.js @@ -0,0 +1,65 @@ +/** + * @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. + */ + +import { h, iconWarning } from '/js/src/index.js'; +import { QcFlagsOverviewComponenet } from '../Overview/qcFlagOverviewComponent.js'; +import { frontLink } from '../../../components/common/navigation/frontLink.js'; +import { tooltip } from '../../../components/common/popover/tooltip.js'; +import { breadcrumbs } from '../../../components/common/navigation/breadcrumbs.js'; +import spinner from '../../../components/common/spinner.js'; + +/** + * Render Quality Control Flags For Data Pass Overview page + * @param {Model} model The overall model object. + * @returns {Component} The overview page + */ +export const QcFlagsForDataPassOverviewPage = ({ qcFlags: { forDataPassOverviewModel: qcFlagsForDataPassOverviewModel } }) => { + const { + run: remoteRun, + dplDetector: remoteDplDetector, + dataPass: remoteDataPass, + } = qcFlagsForDataPassOverviewModel; + + const commonTitle = h('h2', 'QC'); + return h('', { + onremove: () => qcFlagsForDataPassOverviewModel.reset(), + }, [ + h('.flex-row.justify-between.items-center', [ + h('.flex-row.g1.items-center', [ + remoteDataPass.match({ + Success: (dataPass) => remoteRun.match({ + Success: (run) => remoteDplDetector.match({ + Success: (dplDetector) => breadcrumbs([ + commonTitle, + h('h2', frontLink(dataPass.name, 'runs-per-data-pass', { dataPassId: dataPass.id })), + h('h2', frontLink(run.runNumber, 'run-detail', { runNumber: run.runNumber })), + h('h2', dplDetector.name), + ]), + Failure: () => [commonTitle, tooltip(h('.f3', iconWarning()), 'Not able to load detector info')], + Loading: () => [commonTitle, h('', spinner({ size: 2, absolute: false }))], + NotAsked: () => [commonTitle, tooltip(h('.f3', iconWarning()), 'No detector data was asked for')], + }), + Failure: () => [commonTitle, tooltip(h('.f3', iconWarning()), 'Not able to load run info')], + Loading: () => [commonTitle, h('', spinner({ size: 2, absolute: false }))], + NotAsked: () => [commonTitle, tooltip(h('.f3', iconWarning()), 'No run data was asked for')], + }), + Failure: () => [commonTitle, tooltip(h('.f3', iconWarning()), 'Not able to load data pass info')], + Loading: () => [commonTitle, h('', spinner({ size: 2, absolute: false }))], + NotAsked: () => [commonTitle, tooltip(h('.f3', iconWarning()), 'No data pass data was asked for')], + }), + ]), + ]), + QcFlagsOverviewComponenet(qcFlagsForDataPassOverviewModel), + ]); +}; diff --git a/lib/public/views/QcFlags/ForSimulationPass/QcFlagsForSimulationPassOverviewModel.js b/lib/public/views/QcFlags/ForSimulationPass/QcFlagsForSimulationPassOverviewModel.js new file mode 100644 index 0000000000..d3494b4f3d --- /dev/null +++ b/lib/public/views/QcFlags/ForSimulationPass/QcFlagsForSimulationPassOverviewModel.js @@ -0,0 +1,88 @@ +/** + * @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 { buildUrl } from '../../../utilities/fetch/buildUrl.js'; +import { RemoteData } from '/js/src/index.js'; +import { getRemoteData } from '../../../utilities/fetch/getRemoteData.js'; +import { QcFlagsOverviewModel } from '../Overview/QcFlagsOverviewModel.js'; +import { ObservableData } from '../../../utilities/ObservableData.js'; + +/** + * Quality Control Flags For Simulation Pass overview model + * + * @implements {OverviewModel} + */ +export class QcFlagsForSimulationPassOverviewModel extends QcFlagsOverviewModel { + /** + * The constructor of the Overview model object + */ + constructor() { + super(); + this._simulationPass$ = new ObservableData(RemoteData.notAsked()); + this._simulationPass$.bubbleTo(this); + } + + // eslint-disable-next-line valid-jsdoc + /** + * @inheritdoc + */ + getRootEndpoint() { + const params = { + filter: { + simulationPassIds: [this._simulationPassId], + }, + }; + + return buildUrl(super.getRootEndpoint(), params); + } + + // eslint-disable-next-line valid-jsdoc + /** + * @inheritdoc + */ + load() { + this._fetchSimulationPass(); + super.load(); + } + + /** + * Fetch Simulaiton Pass data which QC flags are fetched + * @return {Promise} promise + * @private + */ + async _fetchSimulationPass() { + this._simulationPass$.setCurrent(RemoteData.loading()); + try { + const { data: simulationPass } = await getRemoteData(`/api/simulationPasses/${this._simulationPassId}`); + this._simulationPass$.setCurrent(RemoteData.success(simulationPass)); + } catch (error) { + this._simulationPass$.setCurrent(RemoteData.failure(error)); + } + } + + /** + * Set id of simulation pass which for QC flags should be fetched + * @param {number} simulationPassId siumulation pass id + */ + set simulationPassId(simulationPassId) { + this._simulationPassId = simulationPassId; + } + + /** + * Get current simulation pass which QC flags are fetched + * @return {RemoteData} simulation pass remote data + */ + get simulationPass() { + return this._simulationPass$.getCurrent(); + } +} diff --git a/lib/public/views/QcFlags/ForSimulationPass/QcFlagsForSimulationPassOverviewPage.js b/lib/public/views/QcFlags/ForSimulationPass/QcFlagsForSimulationPassOverviewPage.js new file mode 100644 index 0000000000..011388b19c --- /dev/null +++ b/lib/public/views/QcFlags/ForSimulationPass/QcFlagsForSimulationPassOverviewPage.js @@ -0,0 +1,66 @@ +/** + * @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. + */ + +import { h, iconWarning } from '/js/src/index.js'; +import { QcFlagsOverviewComponenet } from '../Overview/qcFlagOverviewComponent.js'; +import { frontLink } from '../../../components/common/navigation/frontLink.js'; +import { breadcrumbs } from '../../../components/common/navigation/breadcrumbs.js'; +import { tooltip } from '../../../components/common/popover/tooltip.js'; +import spinner from '../../../components/common/spinner.js'; + +/** + * Render Quality Control Flags For Simulation Pass Overview page + * @param {Model} model The overall model object. + * @returns {Component} The overview page + */ +export const QcFlagsForSimulationPassOverviewPage = ({ qcFlags: + { forSimulationPassOverviewModel: qcFlagsForSimulationPassOverviewModel } }) => { + const { + run: remoteRun, + dplDetector: remoteDplDetector, + simulationPass: remoteSimulationPass, + } = qcFlagsForSimulationPassOverviewModel; + + const commonTitle = h('h2', 'QC'); + return h('', { + onremove: () => qcFlagsForSimulationPassOverviewModel.reset(), + }, [ + h('.flex-row.justify-between.items-center', [ + h('.flex-row.g1.items-center', [ + remoteSimulationPass.match({ + Success: (simulationPass) => remoteRun.match({ + Success: (run) => remoteDplDetector.match({ + Success: (dplDetector) => breadcrumbs([ + commonTitle, + h('h2', frontLink(simulationPass.name, 'runs-per-simulation-pass', { simulationPassId: simulationPass.id })), + h('h2', frontLink(run.runNumber, 'run-detail', { runNumber: run.runNumber })), + h('h2', dplDetector.name), + ]), + Failure: () => [commonTitle, tooltip(h('.f3', iconWarning()), 'Not able to load detector info')], + Loading: () => [commonTitle, h('', spinner({ size: 2, absolute: false }))], + NotAsked: () => [commonTitle, tooltip(h('.f3', iconWarning()), 'No detector data was asked for')], + }), + Failure: () => [commonTitle, tooltip(h('.f3', iconWarning()), 'Not able to load run info')], + Loading: () => [commonTitle, h('', spinner({ size: 2, absolute: false }))], + NotAsked: () => [commonTitle, tooltip(h('.f3', iconWarning()), 'No run data was asked for')], + }), + Failure: () => [commonTitle, tooltip(h('.f3', iconWarning()), 'Not able to load simulation pass info')], + Loading: () => [commonTitle, h('', spinner({ size: 2, absolute: false }))], + NotAsked: () => [commonTitle, tooltip(h('.f3', iconWarning()), 'No simulation pass data was asked for')], + }), + ]), + ]), + QcFlagsOverviewComponenet(qcFlagsForSimulationPassOverviewModel), + ]); +}; diff --git a/lib/public/views/QcFlags/Overview/QcFlagsOverviewModel.js b/lib/public/views/QcFlags/Overview/QcFlagsOverviewModel.js new file mode 100644 index 0000000000..0b53741e6e --- /dev/null +++ b/lib/public/views/QcFlags/Overview/QcFlagsOverviewModel.js @@ -0,0 +1,127 @@ +/** + * @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 { buildUrl } from '../../../utilities/fetch/buildUrl.js'; +import { OverviewPageModel } from '../../../models/OverviewModel.js'; +import { RemoteData } from '/js/src/index.js'; +import { getRemoteData } from '../../../utilities/fetch/getRemoteData.js'; +import { ObservableData } from '../../../utilities/ObservableData.js'; +import { dplDetectorsProvider } from '../../../services/detectors/dplDetectorsProvider.js'; + +/** + * Quality Control Flags overview model + * + * @implements {OverviewModel} + * @abstract + */ +export class QcFlagsOverviewModel extends OverviewPageModel { + /** + * The constructor of the Overview model object + */ + constructor() { + super(); + this._run$ = new ObservableData(RemoteData.notAsked()); + this._run$.bubbleTo(this); + dplDetectorsProvider.physical$.observe(() => this._getDetector()); + this._dplDetector$ = new ObservableData(RemoteData.notAsked()); + this._dplDetector$.bubbleTo(this); + } + + // eslint-disable-next-line valid-jsdoc + /** + * @inheritdoc + */ + getRootEndpoint() { + const params = { + filter: { + runNumbers: [this._runNumber], + dplDetectorIds: [this._dplDetectorId], + }, + }; + return buildUrl('/api/qcFlags', params); + } + + // eslint-disable-next-line valid-jsdoc + /** + * @inheritdoc + */ + async load() { + this._getDetector(); + this._fetchRun(); + super.load(); + } + + /** + * Fetch DPL detector which QC flags should be fetched + * @return {void} + */ + _getDetector() { + this._dplDetector$.setCurrent(dplDetectorsProvider.physical$.getCurrent().match({ + Success: (dplDetectors) => { + const dplDetector = dplDetectors.find(({ id }) => id === this._dplDetectorId); + return dplDetector + ? RemoteData.success(dplDetector) + : RemoteData.failure({ errors: [{ detail: `There is no dplDetector with given id (${this._dplDetectorId})` }] }); + }, + Failure: (payload) => RemoteData.failure(payload), + Loading: () => RemoteData.loading(), + NotAsked: () => RemoteData.notAsked(), + })); + } + + /** + * Fetch run data + * @return {Promise} promise + */ + async _fetchRun() { + this._run$.setCurrent(RemoteData.loading()); + try { + const { data: run } = await getRemoteData(`/api/runs/${this._runNumber}`); + this._run$.setCurrent(RemoteData.success(run)); + } catch (error) { + this._run$.setCurrent(RemoteData.failure(error)); + } + } + + /** + * Set id of DPL detector which for QC flags should be fetched + * @param {number} dplDetectorId detector id + */ + set dplDetectorId(dplDetectorId) { + this._dplDetectorId = dplDetectorId; + } + + /** + * Set runNumber of run which for QC flags should be fetched + * @param {number} runNumber runNumber + */ + set runNumber(runNumber) { + this._runNumber = runNumber; + } + + /** + * Run getter + * @return {RemoteData} current run + */ + get run() { + return this._run$.getCurrent(); + } + + /** + * Detector getter + * @return {RemoteData} current detector + */ + get dplDetector() { + return this._dplDetector$.getCurrent(); + } +} diff --git a/lib/public/views/QcFlags/Overview/qcFlagOverviewComponent.js b/lib/public/views/QcFlags/Overview/qcFlagOverviewComponent.js new file mode 100644 index 0000000000..8628ffd7bd --- /dev/null +++ b/lib/public/views/QcFlags/Overview/qcFlagOverviewComponent.js @@ -0,0 +1,50 @@ +/** + * @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. + */ + +import { h } from '/js/src/index.js'; +import { table } from '../../../components/common/table/table.js'; +import { paginationComponent } from '../../../components/Pagination/paginationComponent.js'; +import { estimateDisplayableRowsCount } from '../../../utilities/estimateDisplayableRowsCount.js'; +import { qcFlagsActiveColumns } from '../ActiveColumns/qcFlagsActiveColumns.js'; + +const TABLEROW_HEIGHT = 35; +// Estimate of the navbar and pagination elements height total; Needs to be updated in case of changes; +const PAGE_USED_HEIGHT = 215; + +/** + * Render Quality Control Flags Overview component + * @param {QcFlagsOverviewModel} qcFlagsModel QC flags overview model + * @returns {Component} The overview component + */ +export const QcFlagsOverviewComponenet = (qcFlagsModel) => { + qcFlagsModel.pagination.provideDefaultItemsPerPage(estimateDisplayableRowsCount( + TABLEROW_HEIGHT, + PAGE_USED_HEIGHT, + )); + + const { + items: qcFlags, + } = qcFlagsModel; + + return h('.w-100.flex-column', [ + table( + qcFlags, + qcFlagsActiveColumns, + { classes: '.table-sm' }, + null, + { sort: qcFlagsModel.sortModel }, + ), + paginationComponent(qcFlagsModel.pagination), + ]); +}; diff --git a/lib/public/views/QcFlags/QcFlagsModel.js b/lib/public/views/QcFlags/QcFlagsModel.js new file mode 100644 index 0000000000..edf62ef4c2 --- /dev/null +++ b/lib/public/views/QcFlags/QcFlagsModel.js @@ -0,0 +1,76 @@ +/** + * @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 { QcFlagsForDataPassOverviewModel } from './ForDataPass/QcFlagsForDataPassOverviewModel.js'; +import { QcFlagsForSimulationPassOverviewModel } from './ForSimulationPass/QcFlagsForSimulationPassOverviewModel.js'; +import { Observable } from '/js/src/index.js'; + +/** + * Quality Control Flags model + */ +export class QcFlagsModel extends Observable { + /** + * The constructor of the model + */ + constructor() { + super(); + + this._forDataPassOverviewModel = new QcFlagsForDataPassOverviewModel(); + this._forDataPassOverviewModel.bubbleTo(this); + + this._forSimulationPassOverviewModel = new QcFlagsForSimulationPassOverviewModel(); + this._forSimulationPassOverviewModel.bubbleTo(this); + } + + /** + * Load the overview page model + * + * @returns {void} + */ + loadForDataPassOverview({ dataPassId, runNumber, dplDetectorId }) { + this._forDataPassOverviewModel.runNumber = parseInt(runNumber, 10); + this._forDataPassOverviewModel.dataPassId = parseInt(dataPassId, 10); + this._forDataPassOverviewModel.dplDetectorId = parseInt(dplDetectorId, 10); + this._forDataPassOverviewModel.load(); + } + + /** + * Load the overview page model + * + * @returns {void} + */ + loadForSimulationPassOverview({ simulationPassId, runNumber, dplDetectorId }) { + this._forSimulationPassOverviewModel.runNumber = parseInt(runNumber, 10); + this._forSimulationPassOverviewModel.simulationPassId = parseInt(simulationPassId, 10); + this._forSimulationPassOverviewModel.dplDetectorId = parseInt(dplDetectorId, 10); + this._forSimulationPassOverviewModel.load(); + } + + /** + * Returns the model for the overview page + * + * @return {QcFlagsForDataPassOverviewModel} the overview model + */ + get forDataPassOverviewModel() { + return this._forDataPassOverviewModel; + } + + /** + * Returns the model for the overview page + * + * @return {QcFlagsForSimulationPassOverviewModel} the overview model + */ + get forSimulationPassOverviewModel() { + return this._forSimulationPassOverviewModel; + } +} diff --git a/lib/public/views/QcFlags/common/qcFlagTypeColoredBadge.js b/lib/public/views/QcFlags/common/qcFlagTypeColoredBadge.js new file mode 100644 index 0000000000..589ea9910f --- /dev/null +++ b/lib/public/views/QcFlags/common/qcFlagTypeColoredBadge.js @@ -0,0 +1,23 @@ +/** + * @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. + */ + +import { h } from '/js/src/index.js'; + +/** + * Create colored badge (marker) for QC flag type + * @param {QcFlagType} qcFlagType quality control flag type + * @return {Component} badge + */ +export const qcFlagTypeColoredBadge = ({ color }) => h('.badge', { + style: { 'background-color': color } }); diff --git a/lib/public/views/Runs/ActiveColumns/runDetectorsAsyncQcActiveColumns.js b/lib/public/views/Runs/ActiveColumns/runDetectorsAsyncQcActiveColumns.js new file mode 100644 index 0000000000..a3c0e2c6fe --- /dev/null +++ b/lib/public/views/Runs/ActiveColumns/runDetectorsAsyncQcActiveColumns.js @@ -0,0 +1,63 @@ +/** + * @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 { h } from '/js/src/index.js'; +import { frontLink } from '../../../components/common/navigation/frontLink.js'; + +/** + * Factory for detectors related active columns configuration + * @param {DplDetector[]} dplDetectors detectors list + * @param {object} [options] additional options + * @param {object|string|string[]} [options.profiles] profiles to which the column is restricted to + * @param {number} [options.dataPassId] if provided a cell become link to QC flag page for given data pass + * @param {number} [options.simulationPassId] if provided a cell become link to QC flag page for given simulation pass + * @return {object} active columns configuration + */ +export const createRunDetectorsAsyncQcActiveColumns = (dplDetectors, { profiles, dataPassId, simulationPassId } = {}) => + Object.fromEntries(dplDetectors?.map(({ name: detectorName, id: dplDetectorId }) => [ + detectorName, { + name: detectorName.toUpperCase(), + visible: true, + format: (_, run) => { + if (dataPassId && simulationPassId) { + throw new Error('`dataPassId` are `simulationPassId` are exclusive options'); + } + const detectorQualityDisplay = h('.btn.white.bg-primary', 'QC'); + + if (dataPassId) { + return frontLink( + detectorQualityDisplay, + 'qc-flags-for-data-pass', + { + dataPassId, + runNumber: run.runNumber, + dplDetectorId, + }, + ); + } + if (simulationPassId) { + return frontLink( + detectorQualityDisplay, + 'qc-flags-for-simulation-pass', + { + simulationPassId, + runNumber: run.runNumber, + dplDetectorId, + }, + ); + } + + return detectorQualityDisplay; + }, + profiles, + }, + ]) ?? []); diff --git a/lib/public/views/Runs/ActiveColumns/runDetectorsActiveColumns.js b/lib/public/views/Runs/ActiveColumns/runDetectorsSyncQcActiveColumns.js similarity index 82% rename from lib/public/views/Runs/ActiveColumns/runDetectorsActiveColumns.js rename to lib/public/views/Runs/ActiveColumns/runDetectorsSyncQcActiveColumns.js index f992ec4c75..7dbee21aed 100644 --- a/lib/public/views/Runs/ActiveColumns/runDetectorsActiveColumns.js +++ b/lib/public/views/Runs/ActiveColumns/runDetectorsSyncQcActiveColumns.js @@ -14,12 +14,15 @@ import { formatDetectorQuality } from '../format/formatDetectorQuality.js'; /** * Factory for detectors related active columns configuration - * @param {Detector[]} detectors detectors list + * @param {Detector[]|DplDetector[]} detectors detectors list * @param {object} [options] additional options * @param {object|string|string[]} [options.profiles] profiles to which the column is restricted to * @return {object} active columns configuration */ -export const createRunDetectorsActiveColumns = (detectors, { profiles } = {}) => Object.fromEntries(detectors?.map(({ name: detectorName }) => [ +export const createRunDetectorsSyncQcActiveColumns = ( + detectors, + { profiles } = {}, +) => Object.fromEntries(detectors?.map(({ name: detectorName }) => [ detectorName, { name: detectorName.toUpperCase(), visible: true, diff --git a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js index b62cf7bd67..f2e18b389c 100644 --- a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js +++ b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewModel.js @@ -12,10 +12,10 @@ */ import { RemoteData } from '/js/src/index.js'; import { buildUrl } from '../../../utilities/fetch/buildUrl.js'; -import { detectorsProvider } from '../../../services/detectors/detectorsProvider.js'; import { RunsOverviewModel } from '../Overview/RunsOverviewModel.js'; import { ObservableData } from '../../../utilities/ObservableData.js'; import { getRemoteDataSlice } from '../../../utilities/fetch/getRemoteDataSlice.js'; +import { dplDetectorsProvider } from '../../../services/detectors/dplDetectorsProvider.js'; /** * Runs Per Data Pass overview model @@ -27,7 +27,7 @@ export class RunsPerDataPassOverviewModel extends RunsOverviewModel { */ constructor(model) { super(model); - this._detectors$ = detectorsProvider.physical$; + this._detectors$ = dplDetectorsProvider.physical$; this._detectors$.bubbleTo(this); this._dataPass = new ObservableData(RemoteData.notAsked()); this._dataPass.bubbleTo(this); @@ -87,7 +87,7 @@ export class RunsPerDataPassOverviewModel extends RunsOverviewModel { /** * Get all detectors - * @return {RemoteData} detectors + * @return {RemoteData} detectors */ get detectors() { return this._detectors$.getCurrent(); diff --git a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js index 758f349e51..ad5ade856c 100644 --- a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js +++ b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js @@ -13,7 +13,6 @@ import { h, iconWarning } from '/js/src/index.js'; import { table } from '../../../components/common/table/table.js'; -import { createRunDetectorsActiveColumns } from '../ActiveColumns/runDetectorsActiveColumns.js'; import { paginationComponent } from '../../../components/Pagination/paginationComponent.js'; import { estimateDisplayableRowsCount } from '../../../utilities/estimateDisplayableRowsCount.js'; import { exportRunsTriggerAndModal } from '../Overview/exportRunsTriggerAndModal.js'; @@ -21,6 +20,7 @@ import { runsActiveColumns } from '../ActiveColumns/runsActiveColumns.js'; import spinner from '../../../components/common/spinner.js'; import { tooltip } from '../../../components/common/popover/tooltip.js'; import { breadcrumbs } from '../../../components/common/navigation/breadcrumbs.js'; +import { createRunDetectorsAsyncQcActiveColumns } from '../ActiveColumns/runDetectorsAsyncQcActiveColumns.js'; const TABLEROW_HEIGHT = 59; // Estimate of the navbar and pagination elements height total; Needs to be updated in case of changes; @@ -38,14 +38,20 @@ export const RunsPerDataPassOverviewPage = ({ runs: { perDataPassOverviewModel } PAGE_USED_HEIGHT, )); - const { items: runs, detectors, dataPass } = perDataPassOverviewModel; + const { items: runs, detectors: remoteDetectors, dataPass: remoteDataPass } = perDataPassOverviewModel; const activeColumns = { ...runsActiveColumns, - ...createRunDetectorsActiveColumns(detectors.match({ + ...createRunDetectorsAsyncQcActiveColumns(remoteDetectors.match({ Success: (payload) => payload, Other: () => [], - }), { profiles: 'runsPerDataPass' }), + }), { + profiles: 'runsPerDataPass', + dataPassId: remoteDataPass.match({ + Success: (dataPass) => dataPass.id, + Other: () => null, + }), + }), }; const commonTitle = h('h2', { style: 'white-space: nowrap;' }, 'Physics Runs'); @@ -54,7 +60,7 @@ export const RunsPerDataPassOverviewPage = ({ runs: { perDataPassOverviewModel } h('.flex-row.justify-between.items-center.g2', [ h( '.flex-row.g1.items-center', - dataPass.match({ + remoteDataPass.match({ Success: (payload) => breadcrumbs([commonTitle, h('h2', payload.name)]), Failure: () => [commonTitle, tooltip(h('.f3', iconWarning()), 'Not able to fetch data pass info')], Loading: () => [commonTitle, spinner({ size: 2, absolute: false })], diff --git a/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js b/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js index bf1c29e173..d88dece0ce 100644 --- a/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js +++ b/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewModel.js @@ -11,8 +11,8 @@ * or submit itself to any jurisdiction. */ import { buildUrl } from '../../../utilities/fetch/buildUrl.js'; -import { detectorsProvider } from '../../../services/detectors/detectorsProvider.js'; import { RunsOverviewModel } from '../Overview/RunsOverviewModel.js'; +import { dplDetectorsProvider } from '../../../services/detectors/dplDetectorsProvider.js'; /** * Runs Per LHC Period overview model @@ -24,7 +24,7 @@ export class RunsPerLhcPeriodOverviewModel extends RunsOverviewModel { */ constructor(model) { super(model); - this._detectors$ = detectorsProvider.physical$; + this._detectors$ = dplDetectorsProvider.physical$; this._detectors$.bubbleTo(this); } diff --git a/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewPage.js b/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewPage.js index e3b7c102b5..dabae1ae87 100644 --- a/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewPage.js +++ b/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewPage.js @@ -13,11 +13,11 @@ import { h } from '/js/src/index.js'; import { table } from '../../../components/common/table/table.js'; -import { createRunDetectorsActiveColumns } from '../ActiveColumns/runDetectorsActiveColumns.js'; import { paginationComponent } from '../../../components/Pagination/paginationComponent.js'; import { estimateDisplayableRowsCount } from '../../../utilities/estimateDisplayableRowsCount.js'; import { exportRunsTriggerAndModal } from '../Overview/exportRunsTriggerAndModal.js'; import { runsActiveColumns } from '../ActiveColumns/runsActiveColumns.js'; +import { createRunDetectorsSyncQcActiveColumns } from '../ActiveColumns/runDetectorsSyncQcActiveColumns.js'; const TABLEROW_HEIGHT = 59; // Estimate of the navbar and pagination elements height total; Needs to be updated in case of changes; @@ -35,11 +35,11 @@ export const RunsPerLhcPeriodOverviewPage = ({ runs: { perLhcPeriodOverviewModel PAGE_USED_HEIGHT, )); - const { items: runs, detectors, lhcPeriodName } = perLhcPeriodOverviewModel; + const { items: runs, detectors: remoteDetectors, lhcPeriodName } = perLhcPeriodOverviewModel; const activeColumns = { ...runsActiveColumns, - ...createRunDetectorsActiveColumns(detectors.match({ + ...createRunDetectorsSyncQcActiveColumns(remoteDetectors.match({ Success: (payload) => payload, Other: () => [], }), { profiles: 'runsPerLhcPeriod' }), @@ -51,7 +51,7 @@ export const RunsPerLhcPeriodOverviewPage = ({ runs: { perLhcPeriodOverviewModel exportRunsTriggerAndModal(perLhcPeriodOverviewModel, modalModel), ]), h('.flex-column.w-100', [ - table(detectors.isSuccess() ? runs : detectors, activeColumns, null, { profile: 'runsPerLhcPeriod' }), + table(remoteDetectors.isSuccess() ? runs : remoteDetectors, activeColumns, null, { profile: 'runsPerLhcPeriod' }), paginationComponent(perLhcPeriodOverviewModel.pagination), ]), ]); diff --git a/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js b/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js index 0abd8eaa3e..0954808b4e 100644 --- a/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js +++ b/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewModel.js @@ -12,10 +12,10 @@ */ import { RemoteData } from '/js/src/index.js'; import { buildUrl } from '../../../utilities/fetch/buildUrl.js'; -import { detectorsProvider } from '../../../services/detectors/detectorsProvider.js'; import { RunsOverviewModel } from '../Overview/RunsOverviewModel.js'; import { ObservableData } from '../../../utilities/ObservableData.js'; import { getRemoteData } from '../../../utilities/fetch/getRemoteData.js'; +import { dplDetectorsProvider } from '../../../services/detectors/dplDetectorsProvider.js'; /** * Runs Per Simulation Pass overview model @@ -27,7 +27,7 @@ export class RunsPerSimulationPassOverviewModel extends RunsOverviewModel { */ constructor(model) { super(model); - this._detectors$ = detectorsProvider.physical$; + this._detectors$ = dplDetectorsProvider.physical$; this._detectors$.bubbleTo(this); this._simulationPass$ = new ObservableData(RemoteData.notAsked()); this._simulationPass$.bubbleTo(this); @@ -88,7 +88,7 @@ export class RunsPerSimulationPassOverviewModel extends RunsOverviewModel { /** * Get all detectors - * @return {RemoteData} detectors + * @return {RemoteData} detectors */ get detectors() { return this._detectors$.getCurrent(); diff --git a/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewPage.js b/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewPage.js index 6a4f347a70..bc7aeaaad8 100644 --- a/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewPage.js +++ b/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewPage.js @@ -13,7 +13,6 @@ import { h, iconWarning } from '/js/src/index.js'; import { table } from '../../../components/common/table/table.js'; -import { createRunDetectorsActiveColumns } from '../ActiveColumns/runDetectorsActiveColumns.js'; import { paginationComponent } from '../../../components/Pagination/paginationComponent.js'; import { estimateDisplayableRowsCount } from '../../../utilities/estimateDisplayableRowsCount.js'; import { exportRunsTriggerAndModal } from '../Overview/exportRunsTriggerAndModal.js'; @@ -21,6 +20,7 @@ import { runsActiveColumns } from '../ActiveColumns/runsActiveColumns.js'; import { breadcrumbs } from '../../../components/common/navigation/breadcrumbs.js'; import { tooltip } from '../../../components/common/popover/tooltip.js'; import spinner from '../../../components/common/spinner.js'; +import { createRunDetectorsAsyncQcActiveColumns } from '../ActiveColumns/runDetectorsAsyncQcActiveColumns.js'; const TABLEROW_HEIGHT = 59; // Estimate of the navbar and pagination elements height total; Needs to be updated in case of changes; @@ -43,10 +43,16 @@ export const RunsPerSimulationPassOverviewPage = ({ const activeColumns = { ...runsActiveColumns, - ...createRunDetectorsActiveColumns(remoteDetectors.match({ + ...createRunDetectorsAsyncQcActiveColumns(remoteDetectors.match({ Success: (detectors) => detectors, Other: () => [], - }), { profiles: ['runsPerSimulationPass'] }), + }), { + profiles: ['runsPerSimulationPass'], + simulationPassId: remoteSimulationPass.match({ + Success: (simulationPass) => simulationPass.id, + Other: () => null, + }), + }), }; const commonTitle = h('h2', 'Runs per MC'); diff --git a/test/public/index.js b/test/public/index.js index 7807c136d1..b54148c200 100644 --- a/test/public/index.js +++ b/test/public/index.js @@ -25,6 +25,7 @@ const LhcPeriodsSuite = require('./lhcPeriods'); const DataPassesSuite = require('./dataPasses'); const SimulationPassesSuite = require('./simulationPasses'); const QcFlagTypesSuite = require('./qcFlagTypes'); +const QcFlagsSuite = require('./qcFlags'); module.exports = () => { describe('LhcPeriods', LhcPeriodsSuite); @@ -41,4 +42,5 @@ module.exports = () => { describe('DataPasses', DataPassesSuite); describe('SimulationPasses', SimulationPassesSuite); describe('QcFlagTypes', QcFlagTypesSuite); + describe('QcFlags', QcFlagsSuite); }; diff --git a/test/public/qcFlags/forDataPassOverview.test.js b/test/public/qcFlags/forDataPassOverview.test.js new file mode 100644 index 0000000000..584d00169f --- /dev/null +++ b/test/public/qcFlags/forDataPassOverview.test.js @@ -0,0 +1,156 @@ +/** + * @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 chai = require('chai'); +const { + defaultBefore, + defaultAfter, + expectInnerText, + pressElement, + goToPage, + validateTableData, + fillInput, + validateElement, + checkMismatchingUrlParam, +} = require('../defaults'); + +const { expect } = chai; +const dateAndTime = require('date-and-time'); + +module.exports = () => { + let page; + let browser; + + before(async () => { + [page, browser] = await defaultBefore(page, browser); + await page.setViewport({ + width: 1200, + height: 720, + deviceScaleFactor: 1, + }); + }); + + after(async () => { + [page, browser] = await defaultAfter(page, browser); + }); + + it('loads the page successfully', async () => { + const response = await goToPage(page, 'qc-flags-for-data-pass', { queryParameters: { + dataPassId: 1, + runNumber: 106, + dplDetectorId: 1, + } }); + + // We expect the page to return the correct status code, making sure the server is running properly + expect(response.status()).to.equal(200); + + // We expect the page to return the correct title, making sure there isn't another server running on this port + const title = await page.title(); + expect(title).to.equal('AliceO2 Bookkeeping'); + + await expectInnerText(page, 'h2:nth-of-type(1)', 'QC'); + await expectInnerText(page, 'h2:nth-of-type(2)', 'LHC22b_apass1'); + await expectInnerText(page, 'h2:nth-of-type(3)', '106'); + await expectInnerText(page, 'h2:nth-of-type(4)', 'CPV'); + }); + + it('can naviagate to runs per data pass page from breadcrumbs link', async () => { + await goToPage(page, 'qc-flags-for-data-pass', { queryParameters: { + dataPassId: 1, + runNumber: 106, + dplDetectorId: 1, + } }); + + await pressElement(page, 'h2:nth-of-type(2)'); + expect(await checkMismatchingUrlParam(page, { page: 'runs-per-data-pass', dataPassId: '1' })).to.be.eql({}); + }); + + it('can naviagate to run details page from breadcrumbs link', async () => { + await goToPage(page, 'qc-flags-for-data-pass', { queryParameters: { + dataPassId: 1, + runNumber: 106, + dplDetectorId: 1, + } }); + + await pressElement(page, 'h2:nth-of-type(3)'); + expect(await checkMismatchingUrlParam(page, { page: 'run-detail', runNumber: '106' })).to.be.eql({}); + }); + + it('shows correct datatypes in respective columns', async () => { + await goToPage(page, 'qc-flags-for-data-pass', { queryParameters: { + dataPassId: 1, + runNumber: 106, + dplDetectorId: 1, + } }); + + // eslint-disable-next-line require-jsdoc + const validateDate = (date) => date === '-' || !isNaN(dateAndTime.parse(date, 'DD/MM/YYYY hh:mm:ss')); + const tableDataValidators = { + flagType: (flagType) => flagType && flagType !== '-', + createdBy: (userName) => userName && userName !== '-', + from: (timestamp) => timestamp === 'Whole run coverage' || validateDate(timestamp), + to: (timestamp) => timestamp === 'Whole run coverage' || validateDate(timestamp), + createdAt: validateDate, + updatedAt: validateDate, + }; + + await validateTableData(page, new Map(Object.entries(tableDataValidators))); + }); + + it('Should display the correct items counter at the bottom of the page', async () => { + await goToPage(page, 'qc-flags-for-data-pass', { queryParameters: { + dataPassId: 1, + runNumber: 106, + dplDetectorId: 1, + } }); + + await expectInnerText(page, '#firstRowIndex', '1'); + await expectInnerText(page, '#lastRowIndex', '3'); + await expectInnerText(page, '#totalRowsCount', '3'); + }); + + it('can set how many entires are available per page', async () => { + await goToPage(page, 'qc-flags-for-data-pass', { queryParameters: { + dataPassId: 1, + runNumber: 106, + dplDetectorId: 1, + } }); + + const amountSelectorId = '#amountSelector'; + const amountSelectorButtonSelector = `${amountSelectorId} button`; + await pressElement(page, amountSelectorButtonSelector); + + await validateElement(page, `${amountSelectorId} .dropup-menu`); + + const amountItems5 = `${amountSelectorId} .dropup-menu .menu-item:first-child`; + await pressElement(page, amountItems5); + + await fillInput(page, `${amountSelectorId} input[type=number]`, 1111); + await validateElement(page, amountSelectorId); + }); + + it('notifies if table loading returned an error', async () => { + await goToPage(page, 'qc-flags-for-data-pass', { queryParameters: { + dataPassId: 1, + runNumber: 106, + dplDetectorId: 1, + } }); + + // eslint-disable-next-line no-return-assign, no-undef + await page.evaluate(() => model.qcFlags.forDataPassOverviewModel.pagination.itemsPerPage = 200); + + // We expect there to be a fitting error message + const expectedMessage = 'Invalid Attribute: "query.page.limit" must be less than or equal to 100'; + await expectInnerText(page, '.alert-danger', expectedMessage); + }); +}; diff --git a/test/public/qcFlags/forSimulationPassOverview.test.js b/test/public/qcFlags/forSimulationPassOverview.test.js new file mode 100644 index 0000000000..3a21c1a4b6 --- /dev/null +++ b/test/public/qcFlags/forSimulationPassOverview.test.js @@ -0,0 +1,156 @@ +/** + * @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 chai = require('chai'); +const { + defaultBefore, + defaultAfter, + expectInnerText, + pressElement, + goToPage, + validateTableData, + fillInput, + validateElement, + checkMismatchingUrlParam, +} = require('../defaults'); + +const { expect } = chai; +const dateAndTime = require('date-and-time'); + +module.exports = () => { + let page; + let browser; + + before(async () => { + [page, browser] = await defaultBefore(page, browser); + await page.setViewport({ + width: 1200, + height: 720, + deviceScaleFactor: 1, + }); + }); + + after(async () => { + [page, browser] = await defaultAfter(page, browser); + }); + + it('loads the page successfully', async () => { + const response = await goToPage(page, 'qc-flags-for-simulation-pass', { queryParameters: { + simulationPassId: 1, + runNumber: 106, + dplDetectorId: 1, + } }); + + // We expect the page to return the correct status code, making sure the server is running properly + expect(response.status()).to.equal(200); + + // We expect the page to return the correct title, making sure there isn't another server running on this port + const title = await page.title(); + expect(title).to.equal('AliceO2 Bookkeeping'); + + await expectInnerText(page, 'h2:nth-of-type(1)', 'QC'); + await expectInnerText(page, 'h2:nth-of-type(2)', 'LHC23k6c'); + await expectInnerText(page, 'h2:nth-of-type(3)', '106'); + await expectInnerText(page, 'h2:nth-of-type(4)', 'CPV'); + }); + + it('can naviagate to runs per simulation pass page from breadcrumbs link', async () => { + await goToPage(page, 'qc-flags-for-simulation-pass', { queryParameters: { + simulationPassId: 1, + runNumber: 106, + dplDetectorId: 1, + } }); + + await pressElement(page, 'h2:nth-of-type(2)'); + expect(await checkMismatchingUrlParam(page, { page: 'runs-per-simulation-pass', simulationPassId: '1' })).to.be.eql({}); + }); + + it('can naviagate to run details page from breadcrumbs link', async () => { + await goToPage(page, 'qc-flags-for-simulation-pass', { queryParameters: { + simulationPassId: 1, + runNumber: 106, + dplDetectorId: 1, + } }); + + await pressElement(page, 'h2:nth-of-type(3)'); + expect(await checkMismatchingUrlParam(page, { page: 'run-detail', runNumber: '106' })).to.be.eql({}); + }); + + it('shows correct datatypes in respective columns', async () => { + await goToPage(page, 'qc-flags-for-simulation-pass', { queryParameters: { + simulationPassId: 1, + runNumber: 106, + dplDetectorId: 1, + } }); + + // eslint-disable-next-line require-jsdoc + const validateDate = (date) => date === '-' || !isNaN(dateAndTime.parse(date, 'DD/MM/YYYY hh:mm:ss')); + const tableDataValidators = { + flagType: (flagType) => flagType && flagType !== '-', + createdBy: (userName) => userName && userName !== '-', + from: (timestamp) => timestamp === 'Whole run coverage' || validateDate(timestamp), + to: (timestamp) => timestamp === 'Whole run coverage' || validateDate(timestamp), + createdAt: validateDate, + updatedAt: validateDate, + }; + + await validateTableData(page, new Map(Object.entries(tableDataValidators))); + }); + + it('Should display the correct items counter at the bottom of the page', async () => { + await goToPage(page, 'qc-flags-for-simulation-pass', { queryParameters: { + simulationPassId: 1, + runNumber: 106, + dplDetectorId: 1, + } }); + + await expectInnerText(page, '#firstRowIndex', '1'); + await expectInnerText(page, '#lastRowIndex', '1'); + await expectInnerText(page, '#totalRowsCount', '1'); + }); + + it('can set how many entires are available per page', async () => { + await goToPage(page, 'qc-flags-for-simulation-pass', { queryParameters: { + simulationPassId: 1, + runNumber: 106, + dplDetectorId: 1, + } }); + + const amountSelectorId = '#amountSelector'; + const amountSelectorButtonSelector = `${amountSelectorId} button`; + await pressElement(page, amountSelectorButtonSelector); + + await validateElement(page, `${amountSelectorId} .dropup-menu`); + + const amountItems5 = `${amountSelectorId} .dropup-menu .menu-item:first-child`; + await pressElement(page, amountItems5); + + await fillInput(page, `${amountSelectorId} input[type=number]`, 1111); + await validateElement(page, amountSelectorId); + }); + + it('notifies if table loading returned an error', async () => { + await goToPage(page, 'qc-flags-for-simulation-pass', { queryParameters: { + simulationPassId: 1, + runNumber: 106, + dplDetectorId: 1, + } }); + + // eslint-disable-next-line no-return-assign, no-undef + await page.evaluate(() => model.qcFlags.forSimulationPassOverviewModel.pagination.itemsPerPage = 200); + + // We expect there to be a fitting error message + const expectedMessage = 'Invalid Attribute: "query.page.limit" must be less than or equal to 100'; + await expectInnerText(page, '.alert-danger', expectedMessage); + }); +}; diff --git a/test/public/qcFlags/index.js b/test/public/qcFlags/index.js new file mode 100644 index 0000000000..3c97921356 --- /dev/null +++ b/test/public/qcFlags/index.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 QcFlagForDataPassOverviewSuite = require('./forDataPassOverview.test'); +const QcFlagForSimulationPassOverviewSuite = require('./forSimulationPassOverview.test'); + +module.exports = () => { + describe('For Data Pass Overview Page', QcFlagForDataPassOverviewSuite); + describe('For Simulation Pass Overview Page', QcFlagForSimulationPassOverviewSuite); +}; diff --git a/test/public/runs/runsPerDataPass.overview.test.js b/test/public/runs/runsPerDataPass.overview.test.js index 3aab4ccfe3..e3512dc078 100644 --- a/test/public/runs/runsPerDataPass.overview.test.js +++ b/test/public/runs/runsPerDataPass.overview.test.js @@ -23,7 +23,6 @@ const { goToPage, reloadPage, } = require('../defaults'); -const { RUN_QUALITIES } = require('../../../lib/domain/enums/RunQualities.js'); const { waitForDownload } = require('../../utilities/waitForDownload'); const { waitForTimeout } = require('../defaults.js'); @@ -97,7 +96,7 @@ module.exports = () => { timeTrgEnd: (date) => !isNaN(Date.parse(date)), aliceL3Current: (current) => !isNaN(Number(current)), aliceL3Dipole: (current) => !isNaN(Number(current)), - ...Object.fromEntries(DETECTORS.map((detectorName) => [detectorName, (quality) => expect(quality).oneOf([...RUN_QUALITIES, ''])])), + ...Object.fromEntries(DETECTORS.map((detectorName) => [detectorName, (quality) => expect(quality).to.be.equal('QC')])), }; // We find the headers matching the datatype keys diff --git a/test/public/runs/runsPerSimulationPass.overview.test.js b/test/public/runs/runsPerSimulationPass.overview.test.js index 24ec84860f..5d4f107258 100644 --- a/test/public/runs/runsPerSimulationPass.overview.test.js +++ b/test/public/runs/runsPerSimulationPass.overview.test.js @@ -26,7 +26,6 @@ const { getInnerText, checkMismatchingUrlParam, } = require('../defaults'); -const { RUN_QUALITIES } = require('../../../lib/domain/enums/RunQualities.js'); const { waitForDownload } = require('../../utilities/waitForDownload'); const { expect } = chai; @@ -98,7 +97,7 @@ module.exports = () => { aliceL3Current: (current) => !isNaN(Number(current.replace(/,/g, ''))), dipoleCurrent: (current) => !isNaN(Number(current.replace(/,/g, ''))), - ...Object.fromEntries(DETECTORS.map((detectorName) => [detectorName, (quality) => expect(quality).oneOf([...RUN_QUALITIES, ''])])), + ...Object.fromEntries(DETECTORS.map((detectorName) => [detectorName, (quality) => expect(quality).to.be.equal('QC')])), }; await validateTableData(page, new Map(Object.entries(tableDataValidators)));