From 9742f1301069f3a957d1b8edc62232fd7c479994 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Thu, 13 Jul 2023 17:02:10 +0200 Subject: [PATCH 01/10] Decrease dependencies --- app/public/Model.js | 4 ++ .../flags}/flagsVisualization.js | 18 ++++---- app/public/views/flags/overview/flagsPanel.js | 7 +-- app/public/views/runs/Runs.js | 43 +++++++++++++++++++ app/public/views/userView.js | 2 +- app/public/views/userView/data/dataPanel.js | 4 +- 6 files changed, 62 insertions(+), 16 deletions(-) rename app/public/{views/flags/visualization => components/flags}/flagsVisualization.js (80%) create mode 100644 app/public/views/runs/Runs.js diff --git a/app/public/Model.js b/app/public/Model.js index df1c6b4a4..f844cae39 100644 --- a/app/public/Model.js +++ b/app/public/Model.js @@ -18,6 +18,7 @@ import ServiceUnavailableModel from './model/ServiceUnavailableModel.js'; import { RCT } from './config.js'; import Flags from './views/flags/Flags.js'; import Detectors from './views/detectors/Detectors.js'; +import Runs from './views/runs/Runs.js'; const { roles } = RCT; export default class Model extends Observable { @@ -37,6 +38,9 @@ export default class Model extends Observable { this.mode = null; this.submodels = {}; + this.runs = new Runs(this); + this.runs.bubbleTo(this); + this.flags = new Flags(this); this.flags.bubbleTo(this); diff --git a/app/public/views/flags/visualization/flagsVisualization.js b/app/public/components/flags/flagsVisualization.js similarity index 80% rename from app/public/views/flags/visualization/flagsVisualization.js rename to app/public/components/flags/flagsVisualization.js index 49c773ace..fb34e4dd5 100644 --- a/app/public/views/flags/visualization/flagsVisualization.js +++ b/app/public/components/flags/flagsVisualization.js @@ -13,9 +13,9 @@ */ import { h } from '/js/src/index.js'; -import flagVisualization from '../../../components/flags/flagVisualization.js'; +import flagVisualization from './flagVisualization.js'; import { RCT } from '../../../../config.js'; -const { pageNames: PN } = RCT; +const { flagReasonColors } = RCT; function filterDistinct(a) { return a.filter((value, index, array) => array.indexOf(value) === index); @@ -30,8 +30,7 @@ const dateFormatter = (sec) => { return h('', h('.skinny', dateString), timeString); }; -export default function flagsVisualization(model, dataPass, run, flagsData) { - const runData = model.fetchedData[PN.runsPerDataPass][dataPass].payload.rows.find((e) => e.run_number.toString() === run.toString()); +export default function flagsVisualization(runData, flagsData) { const { time_start, time_end } = runData; const distinctFlagReasons = filterDistinct(flagsData.map((flag) => flag.flag_reason.replace(/\s+/g, ''))); @@ -42,18 +41,17 @@ export default function flagsVisualization(model, dataPass, run, flagsData) { }, {}); const flagColor = (flagReason) => { - const colors = RCT.flagReasonColors; switch (flagReason) { case 'LimitedAcceptance': - return colors.limitedAcceptance; + return flagReasonColors.limitedAcceptance; case 'Notbad': - return colors.neutral; + return flagReasonColors.neutral; case 'CertifiedbyExpert': - return colors.neutral; + return flagReasonColors.neutral; case 'UnknownQuality': - return colors.neutral; + return flagReasonColors.neutral; default: - return colors.bad; + return flagReasonColors.bad; } }; diff --git a/app/public/views/flags/overview/flagsPanel.js b/app/public/views/flags/overview/flagsPanel.js index e8824494e..dbd433723 100644 --- a/app/public/views/flags/overview/flagsPanel.js +++ b/app/public/views/flags/overview/flagsPanel.js @@ -16,13 +16,13 @@ import { h, iconDataTransferDownload, iconReload, iconShareBoxed } from '/js/src import filter from '../../userView/data/table/filtering/filter.js'; import downloadCSV from '../../../../utils/csvExport.js'; import pageSettings from '../../userView/data/pageSettings/pageSettings.js'; -import flagsVisualization from '../visualization/flagsVisualization.js'; +import flagsVisualization from '../../../components/flags/flagsVisualization.js'; import flagsTable from './flagsTable.js'; import flagBreadCrumbs from '../../../../components/flags/flagBreadcrumbs.js'; import { defaultRunNumbers } from '../../../../utils/defaults.js'; import noSubPageSelected from '../../userView/data/table/noSubPageSelected.js'; -export default function flagsPanel(model, detectors, flags) { +export default function flagsPanel(model, runs, detectors, flags) { const urlParams = model.router.getUrl().searchParams; const dataPassName = urlParams.get('data_pass_name'); @@ -31,6 +31,7 @@ export default function flagsPanel(model, detectors, flags) { const detectorName = detectors.getDetectorName(detector); const flagsData = flags.getFlags(run, detectorName); + const runData = runs.getRun(dataPassName, run); const functionalities = (model) => h('.btn-group', h('button.btn.btn-secondary.icon-only-button', { @@ -84,7 +85,7 @@ export default function flagsPanel(model, detectors, flags) { h('div', functionalities(model))), model.searchFieldsVisible ? filter(model) : '', - flagsVisualization(model, dataPassName, run, flagsData), + flagsVisualization(runData, flagsData), flagsTable(model, flagsData), h('.modal', { id: 'pageSettingsModal' }, h('.modal-content.abs-center.p3', { diff --git a/app/public/views/runs/Runs.js b/app/public/views/runs/Runs.js new file mode 100644 index 000000000..58b6d9af7 --- /dev/null +++ b/app/public/views/runs/Runs.js @@ -0,0 +1,43 @@ +/** + * @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 { RCT } from '../../config.js'; +const { pageNames: PN } = RCT; + +/** + * Model representing handlers for the runs page + * + * @implements {OverviewModel} + */ +export default class Runs extends Observable { + /** + * The constructor of the Overview model object + * + * @param {Model} model Pass the model to access the defined functions + */ + constructor(model) { + super(); + this.model = model; + this._flags = RemoteData.NotAsked(); + } + + getRunsPerDataPass(dataPass) { + return this.model.submodels[this.model.mode].fetchedData[PN.runsPerDataPass][dataPass].payload.rows; + } + + getRun(dataPass, runNumber) { + const runsPerDataPass = this.getRunsPerDataPass(dataPass); + return runsPerDataPass.find((run) => run.run_number.toString() === runNumber.toString()); + } +} diff --git a/app/public/views/userView.js b/app/public/views/userView.js index ae7080269..57ca7bf06 100644 --- a/app/public/views/userView.js +++ b/app/public/views/userView.js @@ -24,7 +24,7 @@ export default function userPanel(model) { h('section.outline-gray.flex-grow.relative.user-panel-main-content', [ h('.scroll-y.absolute-fill', { id: 'user-panel-main-content' }, - dataPanel(submodel, model.detectors, model.flags)), + dataPanel(submodel, model.runs, model.detectors, model.flags)), ]), ]), ]); diff --git a/app/public/views/userView/data/dataPanel.js b/app/public/views/userView/data/dataPanel.js index 1ef819724..1c92e8ee9 100644 --- a/app/public/views/userView/data/dataPanel.js +++ b/app/public/views/userView/data/dataPanel.js @@ -27,14 +27,14 @@ const { pageNames } = RCT; * @returns {*} */ -export default function dataPanel(model, detectors, flags) { +export default function dataPanel(model, runs, detectors, flags) { const { page, index } = model.getCurrentDataPointer(); const data = model.fetchedData[page][index]; return data ? data.match({ NotAsked: () => h('', 'not asked'), Loading: () => spinnerAndReloadView(model), - Success: () => page === pageNames.flags ? flagsPanel(model, detectors, flags) : tablePanel(model, detectors), + Success: () => page === pageNames.flags ? flagsPanel(model, runs, detectors, flags) : tablePanel(model, detectors), Failure: (status) => failureStatusAndReload(model, status), }) : unknownError(model); } From 3807e8624dd24ef95ed2b7adbfdd64d9d9257633 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Thu, 13 Jul 2023 18:02:35 +0200 Subject: [PATCH 02/10] Fetch runs flag summary with the use of runs submodel --- app/public/utilities/fetch/getRemoteData.js | 22 +++++++ .../utilities/fetch/getRemoteDataSlice.js | 46 +++++++++++++++ app/public/utilities/fetch/jsonFetch.js | 57 +++++++++++++++++++ app/public/views/runs/Runs.js | 26 ++++++++- app/public/views/userView/data/dataPanel.js | 2 +- .../views/userView/data/pagesCellsSpecials.js | 17 +----- app/public/views/userView/data/table/row.js | 9 ++- .../views/userView/data/table/tablePanel.js | 8 +-- 8 files changed, 164 insertions(+), 23 deletions(-) create mode 100644 app/public/utilities/fetch/getRemoteData.js create mode 100644 app/public/utilities/fetch/getRemoteDataSlice.js create mode 100644 app/public/utilities/fetch/jsonFetch.js diff --git a/app/public/utilities/fetch/getRemoteData.js b/app/public/utilities/fetch/getRemoteData.js new file mode 100644 index 000000000..7fc31da0f --- /dev/null +++ b/app/public/utilities/fetch/getRemoteData.js @@ -0,0 +1,22 @@ +/** + * @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 { jsonFetch } from './jsonFetch.js'; + +/** + * Wrapper around {@see jsonFetch} sending GET request + * + * @param {string} endpoint the remote endpoint to send request to + * @return {Promise<*>} resolve with the result of the request or reject with the list of errors if any error occurred + */ +export const getRemoteData = (endpoint) => jsonFetch(endpoint, { method: 'GET' }); diff --git a/app/public/utilities/fetch/getRemoteDataSlice.js b/app/public/utilities/fetch/getRemoteDataSlice.js new file mode 100644 index 000000000..5053e3a66 --- /dev/null +++ b/app/public/utilities/fetch/getRemoteDataSlice.js @@ -0,0 +1,46 @@ +/** + * @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. + */ + +/** + * @typedef DataSlice + * @property {number} totalCount the total amount of items in the set the slide is extracted from + * @property {Array} items the list of items contained in the slice + */ + +import { getRemoteData } from './getRemoteData.js'; + +/** + * Fetch a slice of data (for now, slice bounds must already be defined in the endpoint) alongside with the total amount items from which this + * slice is a subset + * + * @param {string} endpoint the remote endpoint to send request to + * @return {Promise} resolve with the page items and metadata, or reject with the list of errors + */ +export const getRemoteDataSlice = async (endpoint) => { + const remoteJson = await getRemoteData(endpoint); + + const { data = [], meta = { page: { totalItems: 0 } } } = remoteJson || {}; + + if (!Array.isArray(data)) { + return Promise.reject([ + { + title: 'Invalid response', + detail: 'Server returned an invalid response', + }, + ]); + } + + const { totalCount: totalCount = data.length } = meta.page || {}; + + return { items: data, totalCount }; +}; diff --git a/app/public/utilities/fetch/jsonFetch.js b/app/public/utilities/fetch/jsonFetch.js new file mode 100644 index 000000000..b9bbcab7d --- /dev/null +++ b/app/public/utilities/fetch/jsonFetch.js @@ -0,0 +1,57 @@ +/** + * @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 { fetchClient } from '/js/src/index.js'; + +/** + * Send a request to a remote endpoint, and extract the response. If errors occurred, an array of errors containing title and detail is thrown + * + * The endpoint is expected to follow some conventions: + * - If request is valid but no data must be sent, it must return a 204 + * - If data must be returned, the json response must contain a top level "data" key + * - If an error occurred, NO "data" key must be present, and there can be one of the following keys: + * - errors: a list of errors containing title and detail + * - error and message describing the error that occurred + * + * @param {string} endpoint the remote endpoint to send request to + * @param {RequestInit} options the request options, see {@see fetch} native function + * @return {Promise<*>} resolve with the result of the request or reject with the list of errors if any error occurred + */ +export const jsonFetch = async (endpoint, options) => { + let result; + try { + const response = await fetchClient(endpoint, options); + + // 204 means no data and the response do not have a body + if (response.status === 204) { + result = null; + } else { + result = await response.json(); + } + } catch (e) { + result = { + detail: e.message, + }; + } + + if (result === null || result.data) { + return result; + } + + return Promise.reject(result.errors || [ + { + title: result.error || 'Request failure', + detail: result.message || 'An error occurred while fetching data', + }, + ]); +}; diff --git a/app/public/views/runs/Runs.js b/app/public/views/runs/Runs.js index 58b6d9af7..6ce768193 100644 --- a/app/public/views/runs/Runs.js +++ b/app/public/views/runs/Runs.js @@ -14,6 +14,7 @@ import { Observable, RemoteData } from '/js/src/index.js'; import { RCT } from '../../config.js'; const { pageNames: PN } = RCT; +const { dataReqParams: DRP } = RCT; /** * Model representing handlers for the runs page @@ -29,7 +30,22 @@ export default class Runs extends Observable { constructor(model) { super(); this.model = model; - this._flags = RemoteData.NotAsked(); + this._runsPerPeriod = RemoteData.NotAsked(); + this._runsPerDataPass = RemoteData.NotAsked(); + this._runs = RemoteData.NotAsked(); + } + + async fetchRunsPerDataPass(dataPass) { + const submodel = this.model.submodels[this.model.mode]; + this._runsPerDataPass = RemoteData.NotAsked(); + const page = submodel.fetchedData[PN.dataPasses]; + const [pIndex] = Object.keys(page); + const { url } = page[pIndex].payload; + const dpSearchParams = `?page=${PN.runsPerDataPass}&index=${dataPass}&${DRP.rowsOnSite}=50&${DRP.site}=1`; + await submodel.fetchedData.reqForData(true, new URL(url.origin + url.pathname + dpSearchParams)); + + const runNumbers = submodel.fetchedData[PN.runsPerDataPass][dataPass].payload.rows.map((row) => row.run_number); + await this.fetchFlagsSummary(dataPass, runNumbers); } getRunsPerDataPass(dataPass) { @@ -40,4 +56,12 @@ export default class Runs extends Observable { const runsPerDataPass = this.getRunsPerDataPass(dataPass); return runsPerDataPass.find((run) => run.run_number.toString() === runNumber.toString()); } + + async fetchFlagsSummary(dataPass, runNumbers) { + const submodel = this.model.submodels[this.model.mode]; + const url = submodel.router.getUrl(); + const search = `?page=${PN.flags}&data_pass_name=${dataPass}&run_numbers=${runNumbers}&${DRP.rowsOnSite}=50&${DRP.site}=1`; + const flagsUrl = new URL(url.origin + url.pathname + search); + await submodel.fetchedData.reqForData(true, flagsUrl); + } } diff --git a/app/public/views/userView/data/dataPanel.js b/app/public/views/userView/data/dataPanel.js index 1c92e8ee9..0b8d34bdc 100644 --- a/app/public/views/userView/data/dataPanel.js +++ b/app/public/views/userView/data/dataPanel.js @@ -34,7 +34,7 @@ export default function dataPanel(model, runs, detectors, flags) { return data ? data.match({ NotAsked: () => h('', 'not asked'), Loading: () => spinnerAndReloadView(model), - Success: () => page === pageNames.flags ? flagsPanel(model, runs, detectors, flags) : tablePanel(model, detectors), + Success: () => page === pageNames.flags ? flagsPanel(model, runs, detectors, flags) : tablePanel(model, runs, detectors), Failure: (status) => failureStatusAndReload(model, status), }) : unknownError(model); } diff --git a/app/public/views/userView/data/pagesCellsSpecials.js b/app/public/views/userView/data/pagesCellsSpecials.js index 7d12ae3de..04e1db364 100644 --- a/app/public/views/userView/data/pagesCellsSpecials.js +++ b/app/public/views/userView/data/pagesCellsSpecials.js @@ -80,23 +80,12 @@ pagesCellsSpecials[PN.periods] = { }; pagesCellsSpecials[PN.dataPasses] = { - name: (model, item) => [ + name: (model, runs, item) => [ h('td.text-ellipsis', item.name), h('td', h('button.btn.chip.m1', { onclick: async () => { - const page = model.fetchedData[PN.dataPasses]; - const [pIndex] = Object.keys(page); - const { url } = page[pIndex].payload; - const dpSearchParams = `?page=${PN.runsPerDataPass}&index=${item.name}&${DRP.rowsOnSite}=50&${DRP.site}=1`; - await model.fetchedData.reqForData(true, new URL(url.origin + url.pathname + dpSearchParams)); - - const [dpIndex] = Object.keys(model.fetchedData[PN.runsPerDataPass]); - const runNumbers = model.fetchedData[PN.runsPerDataPass][dpIndex].payload.rows.map((row) => row.run_number); - - const search = `?page=${PN.flags}&data_pass_name=${item.name}&run_numbers=${runNumbers}&${DRP.rowsOnSite}=50&${DRP.site}=1`; - const flagsUrl = new URL(url.origin + url.pathname + search); - await model.fetchedData.reqForData(true, flagsUrl); + await runs.fetchRunsPerDataPass(item.name); model.router.go(`/?page=${PN.runsPerDataPass}&index=${item.name}&${DRP.rowsOnSite}=50&${DRP.site}=1&sorting=-run_number`); }, }, 'runs'), @@ -109,7 +98,7 @@ pagesCellsSpecials[PN.dataPasses] = { `/?page=${PN.anchoragePerDatapass}&index=${item.name}&${DRP.rowsOnSite}=50&${DRP.site}=1&sorting=-name`, )), ], - size: (model, item) => getReadableFileSizeString(Number(item.size)), + size: (model, runs, item) => getReadableFileSizeString(Number(item.size)), }; pagesCellsSpecials[PN.mc] = { name: (model, item) => [ diff --git a/app/public/views/userView/data/table/row.js b/app/public/views/userView/data/table/row.js index 741562916..f2e582bcb 100644 --- a/app/public/views/userView/data/table/row.js +++ b/app/public/views/userView/data/table/row.js @@ -17,18 +17,21 @@ import { reduceSerialIf } from '../../../../utils/utils.js'; import detectorIcon from '../../../../components/detectors/detectorIcon.js'; export default function row( - model, visibleFields, data, item, cellsSpecials, index, detectors, + model, visibleFields, data, item, cellsSpecials, index, runs, detectors, ) { + const pageName = model.getCurrentDataPointer().page; const rowDivDef = reduceSerialIf( 'tr.track', ['.row-not-selected', '.d-none'], ['.row-selected', ''], [!item.marked, data.hideMarkedRecords && item.marked], (a, b) => a + b, ); const dataCells = visibleFields.map((field) => - h(`td.${model.getCurrentDataPointer().page}-${field.name.includes('detector') ? 'detector' : field.name}-cell.text-ellipsis`, + h(`td.${pageName}-${field.name.includes('detector') ? 'detector' : field.name}-cell.text-ellipsis`, item[field.name] ? cellsSpecials[field.name] - ? cellsSpecials[field.name](model, item) + ? pageName === 'dataPasses' + ? cellsSpecials[field.name](model, runs, item) + : cellsSpecials[field.name](model, item) : /.*_detector/.test(field.name) ? detectorIcon(model, item, field.name, index, detectors.getDetectorName(field.name)) : item[field.name] diff --git a/app/public/views/userView/data/table/tablePanel.js b/app/public/views/userView/data/table/tablePanel.js index 3082f361b..3483640f4 100644 --- a/app/public/views/userView/data/table/tablePanel.js +++ b/app/public/views/userView/data/table/tablePanel.js @@ -41,7 +41,7 @@ const { pageNames } = RCT; * @returns {*} */ -export default function tablePanel(model, detectors) { +export default function tablePanel(model, runs, detectors) { const dataPointer = model.getCurrentDataPointer(); const data = model.fetchedData[dataPointer.page][dataPointer.index].payload; const page = model.fetchedData[dataPointer.page]; @@ -124,7 +124,7 @@ export default function tablePanel(model, detectors) { }, [ tableHeader(visibleFields, data, model), model.sortingRowVisible ? sortingRow(visibleFields, data, model) : '', - tableBody(model, visibleFields, data, cellsSpecials, dataPointer.page, dataPointer.index, detectors), + tableBody(model, visibleFields, data, cellsSpecials, dataPointer.page, dataPointer.index, runs, detectors), ]), data.rows.length > 15 ? pager(model, data) : '')) : '' @@ -140,11 +140,11 @@ export default function tablePanel(model, detectors) { } function tableBody( - model, visibleFields, data, cellsSpecials, page, index, detectors, + model, visibleFields, data, cellsSpecials, page, index, runs, detectors, ) { return h('tbody', { id: `table-body-${data.url}` }, [postingDataConfig[page] ? postForm(model, data) : ''] .concat(data.rows.map((item) => row( - model, visibleFields, data, item, cellsSpecials, index, detectors, + model, visibleFields, data, item, cellsSpecials, index, runs, detectors, )))); } From 11161f2a6efc2826505d8c475f6f9789ea8481f8 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Thu, 13 Jul 2023 18:13:09 +0200 Subject: [PATCH 03/10] Cleanup :broom: --- app/public/views/runs/Runs.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/app/public/views/runs/Runs.js b/app/public/views/runs/Runs.js index 6ce768193..2c97d306c 100644 --- a/app/public/views/runs/Runs.js +++ b/app/public/views/runs/Runs.js @@ -33,8 +33,17 @@ export default class Runs extends Observable { this._runsPerPeriod = RemoteData.NotAsked(); this._runsPerDataPass = RemoteData.NotAsked(); this._runs = RemoteData.NotAsked(); + this._flagsSummary = RemoteData.NotAsked(); } + async fetchFlagsSummary(dataPass, runNumbers) { + const submodel = this.model.submodels[this.model.mode]; + const url = submodel.router.getUrl(); + const search = `?page=${PN.flags}&data_pass_name=${dataPass}&run_numbers=${runNumbers}&${DRP.rowsOnSite}=50&${DRP.site}=1`; + const flagsUrl = new URL(url.origin + url.pathname + search); + await submodel.fetchedData.reqForData(true, flagsUrl); + } + async fetchRunsPerDataPass(dataPass) { const submodel = this.model.submodels[this.model.mode]; this._runsPerDataPass = RemoteData.NotAsked(); @@ -56,12 +65,4 @@ export default class Runs extends Observable { const runsPerDataPass = this.getRunsPerDataPass(dataPass); return runsPerDataPass.find((run) => run.run_number.toString() === runNumber.toString()); } - - async fetchFlagsSummary(dataPass, runNumbers) { - const submodel = this.model.submodels[this.model.mode]; - const url = submodel.router.getUrl(); - const search = `?page=${PN.flags}&data_pass_name=${dataPass}&run_numbers=${runNumbers}&${DRP.rowsOnSite}=50&${DRP.site}=1`; - const flagsUrl = new URL(url.origin + url.pathname + search); - await submodel.fetchedData.reqForData(true, flagsUrl); - } } From d94d4affe814b0f49dc4e129bab49bbd943ff5a0 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Fri, 14 Jul 2023 10:16:13 +0200 Subject: [PATCH 04/10] Cleanup :broom: --- app/public/model/PrimaryModel.js | 13 +++++++++++++ app/public/views/flags/overview/flagsPanel.js | 2 +- app/public/views/runs/Runs.js | 6 +----- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/app/public/model/PrimaryModel.js b/app/public/model/PrimaryModel.js index a3ec928e4..a3ebca02e 100644 --- a/app/public/model/PrimaryModel.js +++ b/app/public/model/PrimaryModel.js @@ -65,6 +65,19 @@ export default class PrimaryModel extends Observable { default: break; } + + /* + *Switch (this.router.params.page) { + * case 'flags': + * if (! this.router.params['data_pass_name']) { + * const url = `/?page=${pageNames.flags}&run_numbers=-1&${dataReqParams.rowsOnSite}=50&${dataReqParams.site}=1&sorting=-name`; + * this.router.go(url); + * } + * break; + * default: + * console.log('default'); + *} + */ } async logout() { diff --git a/app/public/views/flags/overview/flagsPanel.js b/app/public/views/flags/overview/flagsPanel.js index dbd433723..af7529e95 100644 --- a/app/public/views/flags/overview/flagsPanel.js +++ b/app/public/views/flags/overview/flagsPanel.js @@ -62,7 +62,7 @@ export default function flagsPanel(model, runs, detectors, flags) { onclick: () => model.changeSearchFieldsVisibility(), }, model.searchFieldsVisible ? h('.slider-20-off-white.abs-center') : h('.slider-20-primary.abs-center'))); - return run > defaultRunNumbers + return run > defaultRunNumbers && runData ? h('div.main-content', [ h('div.flex-wrap.justify-between.items-center', h('div.flex-wrap.justify-between.items-center', diff --git a/app/public/views/runs/Runs.js b/app/public/views/runs/Runs.js index 2c97d306c..87654ae2b 100644 --- a/app/public/views/runs/Runs.js +++ b/app/public/views/runs/Runs.js @@ -30,10 +30,6 @@ export default class Runs extends Observable { constructor(model) { super(); this.model = model; - this._runsPerPeriod = RemoteData.NotAsked(); - this._runsPerDataPass = RemoteData.NotAsked(); - this._runs = RemoteData.NotAsked(); - this._flagsSummary = RemoteData.NotAsked(); } async fetchFlagsSummary(dataPass, runNumbers) { @@ -43,7 +39,7 @@ export default class Runs extends Observable { const flagsUrl = new URL(url.origin + url.pathname + search); await submodel.fetchedData.reqForData(true, flagsUrl); } - + async fetchRunsPerDataPass(dataPass) { const submodel = this.model.submodels[this.model.mode]; this._runsPerDataPass = RemoteData.NotAsked(); From a5b3e92b318e6e55968f8af984a4db326d3abcf8 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Fri, 14 Jul 2023 10:19:38 +0200 Subject: [PATCH 05/10] Cleanup :broom: --- app/public/model/PrimaryModel.js | 13 -- app/public/model/data/FetchedDataManager.js | 165 ------------------ app/public/utilities/fetch/getRemoteData.js | 22 --- .../utilities/fetch/getRemoteDataSlice.js | 46 ----- app/public/utilities/fetch/jsonFetch.js | 57 ------ 5 files changed, 303 deletions(-) delete mode 100644 app/public/model/data/FetchedDataManager.js delete mode 100644 app/public/utilities/fetch/getRemoteData.js delete mode 100644 app/public/utilities/fetch/getRemoteDataSlice.js delete mode 100644 app/public/utilities/fetch/jsonFetch.js diff --git a/app/public/model/PrimaryModel.js b/app/public/model/PrimaryModel.js index a3ebca02e..a3ec928e4 100644 --- a/app/public/model/PrimaryModel.js +++ b/app/public/model/PrimaryModel.js @@ -65,19 +65,6 @@ export default class PrimaryModel extends Observable { default: break; } - - /* - *Switch (this.router.params.page) { - * case 'flags': - * if (! this.router.params['data_pass_name']) { - * const url = `/?page=${pageNames.flags}&run_numbers=-1&${dataReqParams.rowsOnSite}=50&${dataReqParams.site}=1&sorting=-name`; - * this.router.go(url); - * } - * break; - * default: - * console.log('default'); - *} - */ } async logout() { diff --git a/app/public/model/data/FetchedDataManager.js b/app/public/model/data/FetchedDataManager.js deleted file mode 100644 index f60c7bac8..000000000 --- a/app/public/model/data/FetchedDataManager.js +++ /dev/null @@ -1,165 +0,0 @@ -/** - * @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 { RemoteData, Loader } from '/js/src/index.js'; - -import FetchedData from './FetchedData.js'; -import { replaceUrlParams } from '../../../utils/utils.js'; -import { RCT } from '../../../config.js'; -const { dataReqParams } = RCT; -const { pageNames } = RCT; - -/** - * Object of this class provide organization of many FetchedData objects, - * each is available as ModelFetchedDataStructure_Object[pageName][index] - * where index is unique identifier of particular data set in chosen page - */ -export default class FetchedDataManager { - constructor(router, model) { - this.model = model; - this.router = router; - this.loader = new Loader(); - - this.rowsOnSite = 50; - - for (const n in pageNames) { - if (Object.prototype.hasOwnProperty.call(pageNames, n)) { - this[n] = {}; - } - } - } - - /** - * Function ask server for data set defined by url field, - * when first after creating object request is performed, - * to url is added additional param 'count-records', - * which inform backend to calculate the total number of rows in target view - * this information is used to create site navigation - */ - - async reqForData(force = false, url = null) { - if (url === null) { - url = this.router.getUrl(); - } - const { page, index } = this.model.getDataPointerFromUrl(url); - const data = this[page][index]; - if (!data || force) { - await this.req(true, url); - } else if (data.payload.url.href !== url.href) { - if (this.diffOnlyBySiteAndSorting(url, data.payload.url)) { - await this.req(false, url); - } else { - await this.req(true, url); - } - } - } - - diffOnlyBySiteAndSorting(url1, url2) { - const p1 = Object.fromEntries(new URLSearchParams(url1.search)); - const p2 = Object.fromEntries(new URLSearchParams(url2.search)); - p1['site'] = undefined; - p1['sorting'] = undefined; - p2['site'] = undefined; - p2['sorting'] = undefined; - - return JSON.stringify(p1) == JSON.stringify(p2); - } - - async req(countAllRecord, url) { - const { page, index } = this.model.getDataPointerFromUrl(url); - - let totalRecordsNumber = null; - if (!countAllRecord) { - ({ totalRecordsNumber } = this[page][index].payload); - } - - const previous = this[page][index]; - this[page][index] = RemoteData.Loading(); - this[page][index].payload = { url: url }; // TODO maybe it should be considered in WebUI - this.model.notify(); - - const reqEndpoint = this.getReqEndpoint(url, countAllRecord); - const { result, status, ok } = await this.model.loader.get(reqEndpoint); - this.model.parent._tokenExpirationHandler(status); - - if (ok) { - const s = RemoteData.Success(new FetchedData(url, result, totalRecordsNumber)); - this[page][index] = s; - previous?.match({ - NotAsked: () => {}, - Loading: () => {}, - Failure: () => {}, - Success: () => { - s.payload.fields = previous.payload.fields; - }, - }); - } else { - this[page][index] = previous; - alert(`${status} ${result?.message}`); - if (result?.message.includes('SESSION_ERROR')) { - this.model.handleSessionError(); - } - this.router.go(previous?.payload?.url ? previous.payload.url : '/'); - } - this.model.notify(); - } - - getReqEndpoint(url, countAllRecord) { - const apiPrefix = `/api${RCT.endpoints.rctData}`; - return apiPrefix + url.pathname.substring(1) + url.search + (countAllRecord ? '&count-records=true' : ''); - } - - changePage(pageNumber) { - const url = this.router.getUrl(); - const newUrl = replaceUrlParams(url, [[dataReqParams.site, pageNumber]]); - this.router.go(newUrl); - } - - changeSorting(sorting) { - const url = this.router.getUrl(); - const { field, order } = sorting; - const newUrl = replaceUrlParams(url, [['sorting', `${order == -1 ? '-' : ''}${field}`]]); - this.router.go(newUrl); - } - - changeRowsOnSite(rowsOnSite) { - const url = this.router.getUrl(); - const newUrl = replaceUrlParams(url, [[dataReqParams.rowsOnSite, rowsOnSite]]); - this.router.go(newUrl); - } - - changeItemStatus(item) { - item.marked = arguments[1] !== undefined - ? arguments[1] - : !item.marked; - this.model.notify(); - } - - changeRecordsVisibility(data) { - data.hideMarkedRecords = !data.hideMarkedRecords; - this.model.notify(); - } - - clear() { - for (const n in pageNames) { - if (Object.prototype.hasOwnProperty.call(pageNames, n)) { - this[n] = {}; - } - } - } - - delete(page, index) { - this[page][index] = undefined; - } -} diff --git a/app/public/utilities/fetch/getRemoteData.js b/app/public/utilities/fetch/getRemoteData.js deleted file mode 100644 index 7fc31da0f..000000000 --- a/app/public/utilities/fetch/getRemoteData.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @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 { jsonFetch } from './jsonFetch.js'; - -/** - * Wrapper around {@see jsonFetch} sending GET request - * - * @param {string} endpoint the remote endpoint to send request to - * @return {Promise<*>} resolve with the result of the request or reject with the list of errors if any error occurred - */ -export const getRemoteData = (endpoint) => jsonFetch(endpoint, { method: 'GET' }); diff --git a/app/public/utilities/fetch/getRemoteDataSlice.js b/app/public/utilities/fetch/getRemoteDataSlice.js deleted file mode 100644 index 5053e3a66..000000000 --- a/app/public/utilities/fetch/getRemoteDataSlice.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * @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. - */ - -/** - * @typedef DataSlice - * @property {number} totalCount the total amount of items in the set the slide is extracted from - * @property {Array} items the list of items contained in the slice - */ - -import { getRemoteData } from './getRemoteData.js'; - -/** - * Fetch a slice of data (for now, slice bounds must already be defined in the endpoint) alongside with the total amount items from which this - * slice is a subset - * - * @param {string} endpoint the remote endpoint to send request to - * @return {Promise} resolve with the page items and metadata, or reject with the list of errors - */ -export const getRemoteDataSlice = async (endpoint) => { - const remoteJson = await getRemoteData(endpoint); - - const { data = [], meta = { page: { totalItems: 0 } } } = remoteJson || {}; - - if (!Array.isArray(data)) { - return Promise.reject([ - { - title: 'Invalid response', - detail: 'Server returned an invalid response', - }, - ]); - } - - const { totalCount: totalCount = data.length } = meta.page || {}; - - return { items: data, totalCount }; -}; diff --git a/app/public/utilities/fetch/jsonFetch.js b/app/public/utilities/fetch/jsonFetch.js deleted file mode 100644 index b9bbcab7d..000000000 --- a/app/public/utilities/fetch/jsonFetch.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @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 { fetchClient } from '/js/src/index.js'; - -/** - * Send a request to a remote endpoint, and extract the response. If errors occurred, an array of errors containing title and detail is thrown - * - * The endpoint is expected to follow some conventions: - * - If request is valid but no data must be sent, it must return a 204 - * - If data must be returned, the json response must contain a top level "data" key - * - If an error occurred, NO "data" key must be present, and there can be one of the following keys: - * - errors: a list of errors containing title and detail - * - error and message describing the error that occurred - * - * @param {string} endpoint the remote endpoint to send request to - * @param {RequestInit} options the request options, see {@see fetch} native function - * @return {Promise<*>} resolve with the result of the request or reject with the list of errors if any error occurred - */ -export const jsonFetch = async (endpoint, options) => { - let result; - try { - const response = await fetchClient(endpoint, options); - - // 204 means no data and the response do not have a body - if (response.status === 204) { - result = null; - } else { - result = await response.json(); - } - } catch (e) { - result = { - detail: e.message, - }; - } - - if (result === null || result.data) { - return result; - } - - return Promise.reject(result.errors || [ - { - title: result.error || 'Request failure', - detail: result.message || 'An error occurred while fetching data', - }, - ]); -}; From bb4648b324d7b2006ef63c5b51b899cf3e5f805d Mon Sep 17 00:00:00 2001 From: Ehevi Date: Fri, 14 Jul 2023 10:21:20 +0200 Subject: [PATCH 06/10] Cleanup --- app/public/model/data/FetchedDataManager.js | 165 ++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 app/public/model/data/FetchedDataManager.js diff --git a/app/public/model/data/FetchedDataManager.js b/app/public/model/data/FetchedDataManager.js new file mode 100644 index 000000000..5b3a6422a --- /dev/null +++ b/app/public/model/data/FetchedDataManager.js @@ -0,0 +1,165 @@ +/** + * @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 { RemoteData, Loader } from '/js/src/index.js'; + +import FetchedData from './FetchedData.js'; +import { replaceUrlParams } from '../../../utils/utils.js'; +import { RCT } from '../../../config.js'; +const { dataReqParams } = RCT; +const { pageNames } = RCT; + +/** + * Object of this class provide organization of many FetchedData objects, + * each is available as ModelFetchedDataStructure_Object[pageName][index] + * where index is unique identifier of particular data set in chosen page + */ +export default class FetchedDataManager { + constructor(router, model) { + this.model = model; + this.router = router; + this.loader = new Loader(); + + this.rowsOnSite = 50; + + for (const n in pageNames) { + if (Object.prototype.hasOwnProperty.call(pageNames, n)) { + this[n] = {}; + } + } + } + + /** + * Function ask server for data set defined by url field, + * when first after creating object request is performed, + * to url is added additional param 'count-records', + * which inform backend to calculate the total number of rows in target view + * this information is used to create site navigation + */ + + async reqForData(force = false, url = null) { + if (url === null) { + url = this.router.getUrl(); + } + const { page, index } = this.model.getDataPointerFromUrl(url); + const data = this[page][index]; + if (!data || force) { + await this.req(true, url); + } else if (data.payload.url.href !== url.href) { + if (this.diffOnlyBySiteAndSorting(url, data.payload.url)) { + await this.req(false, url); + } else { + await this.req(true, url); + } + } + } + + diffOnlyBySiteAndSorting(url1, url2) { + const p1 = Object.fromEntries(new URLSearchParams(url1.search)); + const p2 = Object.fromEntries(new URLSearchParams(url2.search)); + p1['site'] = undefined; + p1['sorting'] = undefined; + p2['site'] = undefined; + p2['sorting'] = undefined; + + return JSON.stringify(p1) == JSON.stringify(p2); + } + + async req(countAllRecord, url) { + const { page, index } = this.model.getDataPointerFromUrl(url); + + let totalRecordsNumber = null; + if (!countAllRecord) { + ({ totalRecordsNumber } = this[page][index].payload); + } + + const previous = this[page][index]; + this[page][index] = RemoteData.Loading(); + this[page][index].payload = { url: url }; // TODO maybe it should be considered in WebUI + this.model.notify(); + + const reqEndpoint = this.getReqEndpoint(url, countAllRecord); + const { result, status, ok } = await this.model.loader.get(reqEndpoint); + this.model.parent._tokenExpirationHandler(status); + + if (ok) { + const s = RemoteData.Success(new FetchedData(url, result, totalRecordsNumber)); + this[page][index] = s; + previous?.match({ + NotAsked: () => {}, + Loading: () => {}, + Failure: () => {}, + Success: () => { + s.payload.fields = previous.payload.fields; + }, + }); + } else { + this[page][index] = previous; + alert(`${status} ${result?.message}`); + if (result?.message.includes('SESSION_ERROR')) { + this.model.handleSessionError(); + } + this.router.go(previous?.payload?.url ? previous.payload.url : '/'); + } + this.model.notify(); + } + + getReqEndpoint(url, countAllRecord) { + const apiPrefix = `/api${RCT.endpoints.rctData}`; + return apiPrefix + url.pathname.substring(1) + url.search + (countAllRecord ? '&count-records=true' : ''); + } + + changePage(pageNumber) { + const url = this.router.getUrl(); + const newUrl = replaceUrlParams(url, [[dataReqParams.site, pageNumber]]); + this.router.go(newUrl); + } + + changeSorting(sorting) { + const url = this.router.getUrl(); + const { field, order } = sorting; + const newUrl = replaceUrlParams(url, [['sorting', `${order == -1 ? '-' : ''}${field}`]]); + this.router.go(newUrl); + } + + changeRowsOnSite(rowsOnSite) { + const url = this.router.getUrl(); + const newUrl = replaceUrlParams(url, [[dataReqParams.rowsOnSite, rowsOnSite]]); + this.router.go(newUrl); + } + + changeItemStatus(item) { + item.marked = arguments[1] !== undefined + ? arguments[1] + : !item.marked; + this.model.notify(); + } + + changeRecordsVisibility(data) { + data.hideMarkedRecords = !data.hideMarkedRecords; + this.model.notify(); + } + + clear() { + for (const n in pageNames) { + if (Object.prototype.hasOwnProperty.call(pageNames, n)) { + this[n] = {}; + } + } + } + + delete(page, index) { + this[page][index] = undefined; + } +} From a84ff10a38904df72c81449a57d1629b602a8d7a Mon Sep 17 00:00:00 2001 From: Ehevi Date: Fri, 14 Jul 2023 15:51:07 +0200 Subject: [PATCH 07/10] Cleanup :broom: --- app/public/model/PrimaryModel.js | 54 +++++++++- app/public/model/data/FetchedDataManager.js | 24 ++--- .../views/flags/overview/flagsContent.js | 98 +++++++++++++++++++ app/public/views/flags/overview/flagsPanel.js | 77 +-------------- app/public/views/runs/Runs.js | 3 +- 5 files changed, 165 insertions(+), 91 deletions(-) create mode 100644 app/public/views/flags/overview/flagsContent.js diff --git a/app/public/model/PrimaryModel.js b/app/public/model/PrimaryModel.js index a3ec928e4..10fa0ed99 100644 --- a/app/public/model/PrimaryModel.js +++ b/app/public/model/PrimaryModel.js @@ -48,11 +48,12 @@ export default class PrimaryModel extends Observable { this.notify(); } - handleLocationChange() { + async handleLocationChange() { const url = this.router.getUrl(); switch (url.pathname) { - case '/': - if (! this.router.params['page']) { + case '/': { + const { page } = this.router.params; + if (! page) { this.router.go(`/?page=${pageNames.periods}&${dataReqParams.rowsOnSite}=50&${dataReqParams.site}=1&sorting=-name`); } else { this.fetchedData.reqForData() @@ -60,11 +61,58 @@ export default class PrimaryModel extends Observable { .catch(() => {}); } break; + } case '/admin/': throw 'TODO'; default: break; } + + /* + * + *Switch (url.pathname) { + * case '/': + * const page = this.router.params['page']; + * if (! page) { + * this.router.go(`/?page=${pageNames.periods}&${dataReqParams.rowsOnSite}=50&${dataReqParams.site}=1&sorting=-name`); + * } else { + * switch (page) { + * case pageNames.flags: + * const dataPassName = this.router.params['data_pass_name']; + * console.log(dataPassName); + * if (dataPassName) { + * /* + * console.log(dataPassName); + * await this.parent.runs.fetchRunsPerDataPass(dataPassName).then(() => {}).catch((e) => {console.log(e)}); + * console.log(this.fetchedData); + * // console.log(this.fetchedData); + * + * const dpSearchParams = `?page=${pageNames.runsPerDataPass}&index=${dataPassName}&${dataReqParams.rowsOnSite}=50&${dataReqParams.site}=1`; + * const dpUrl = new URL(url.origin + url.pathname + dpSearchParams); + * this.fetchedData.reqForData(true, dpUrl).then(() => { + * const runNumbers = this.fetchedData[pageNames.runsPerDataPass][dataPassName].payload.rows.map((row) => row.run_number); + * this.parent.runs.fetchFlagsSummary(dataPassName, runNumbers).then(() => { + * this.fetchedData.reqForData(); + * }).catch(() => {}); + * }); + * + * } else this.goToDefaultPageUrl(pageNames.flags); + * break; + * default: + * this.fetchedData.reqForData() + * .then(() => {}) + * .catch(() => {}); + * break; + * } + * } + * break; + * case '/admin/': + * throw 'TODO'; + * default: + * break; + *} + * + */ } async logout() { diff --git a/app/public/model/data/FetchedDataManager.js b/app/public/model/data/FetchedDataManager.js index 5b3a6422a..f60c7bac8 100644 --- a/app/public/model/data/FetchedDataManager.js +++ b/app/public/model/data/FetchedDataManager.js @@ -1,16 +1,16 @@ /** - * @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. - */ + * @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 { RemoteData, Loader } from '/js/src/index.js'; diff --git a/app/public/views/flags/overview/flagsContent.js b/app/public/views/flags/overview/flagsContent.js new file mode 100644 index 000000000..5a89bf4a5 --- /dev/null +++ b/app/public/views/flags/overview/flagsContent.js @@ -0,0 +1,98 @@ +/** + * @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, iconDataTransferDownload, iconReload, iconShareBoxed } from '/js/src/index.js'; +import filter from '../../userView/data/table/filtering/filter.js'; +import downloadCSV from '../../../../utils/csvExport.js'; +import pageSettings from '../../userView/data/pageSettings/pageSettings.js'; +import flagsVisualization from '../../../components/flags/flagsVisualization.js'; +import flagsTable from './flagsTable.js'; +import flagBreadCrumbs from '../../../../components/flags/flagBreadcrumbs.js'; +import { defaultRunNumbers } from '../../../../utils/defaults.js'; +import noSubPageSelected from '../../userView/data/table/noSubPageSelected.js'; + +export default function flagsContent(model, runs, detectors, flags) { + const urlParams = model.router.getUrl().searchParams; + + const dataPassName = urlParams.get('data_pass_name'); + const run = urlParams.get('run_numbers'); + const detector = urlParams.get('detector'); + + const detectorName = detectors.getDetectorName(detector); + const flagsData = flags.getFlags(run, detectorName); + const runData = runs.getRun(dataPassName, run); + + const functionalities = (model) => h('.btn-group', + h('button.btn.btn-secondary.icon-only-button', { + onclick: () => { + model.fetchedData.reqForData(true); + model.notify(); + }, + }, iconReload()), + + h('button.btn.btn-secondary.icon-only-button', { + onclick: () => { + downloadCSV(model); + }, + }, iconDataTransferDownload()), + + h('button.btn.btn-secondary.icon-only-button', { + onclick: () => { + navigator.clipboard.writeText(model.router.getUrl().toString()) + .then(() => { + }) + .catch(() => { + }); + }, + }, iconShareBoxed()), + + h('button.btn.icon-only-button', { + className: model.searchFieldsVisible ? 'btn-primary' : 'btn-secondary', + onclick: () => model.changeSearchFieldsVisibility(), + }, model.searchFieldsVisible ? h('.slider-20-off-white.abs-center') : h('.slider-20-primary.abs-center'))); + + return run > defaultRunNumbers && runData + ? h('div.main-content', [ + h('div.flex-wrap.justify-between.items-center', + h('div.flex-wrap.justify-between.items-center', + flagBreadCrumbs(model, dataPassName, run, detectorName), + h('button.btn.btn-secondary', { + onclick: () => { + document.getElementById('pageSettingsModal').style.display = 'block'; + document.addEventListener('click', (event) => { + const modalContent = document.getElementsByClassName('modal-content'); + const modal = document.getElementsByClassName('modal'); + if (Array.from(modalContent).find((e) => e != event.target) + && Array.from(modal).find((e) => e == event.target) + && document.getElementById('pageSettingsModal')) { + document.getElementById('pageSettingsModal').style.display = 'none'; + } + }); + }, + }, h('.settings-20'))), + + h('div', functionalities(model))), + model.searchFieldsVisible ? filter(model) : '', + + flagsVisualization(runData, flagsData), + flagsTable(model, flagsData), + h('.modal', { id: 'pageSettingsModal' }, + h('.modal-content.abs-center.p3', { + id: 'pageSettingsModalContent', + }, pageSettings(model, () => { + document.getElementById('pageSettingsModal').style.display = 'none'; + }))), + ]) + : noSubPageSelected(model); +} diff --git a/app/public/views/flags/overview/flagsPanel.js b/app/public/views/flags/overview/flagsPanel.js index af7529e95..a39b9ef5d 100644 --- a/app/public/views/flags/overview/flagsPanel.js +++ b/app/public/views/flags/overview/flagsPanel.js @@ -12,87 +12,16 @@ * or submit itself to any jurisdiction. */ -import { h, iconDataTransferDownload, iconReload, iconShareBoxed } from '/js/src/index.js'; -import filter from '../../userView/data/table/filtering/filter.js'; -import downloadCSV from '../../../../utils/csvExport.js'; -import pageSettings from '../../userView/data/pageSettings/pageSettings.js'; -import flagsVisualization from '../../../components/flags/flagsVisualization.js'; -import flagsTable from './flagsTable.js'; -import flagBreadCrumbs from '../../../../components/flags/flagBreadcrumbs.js'; import { defaultRunNumbers } from '../../../../utils/defaults.js'; import noSubPageSelected from '../../userView/data/table/noSubPageSelected.js'; +import flagsContent from './flagsContent.js'; export default function flagsPanel(model, runs, detectors, flags) { const urlParams = model.router.getUrl().searchParams; - const dataPassName = urlParams.get('data_pass_name'); const run = urlParams.get('run_numbers'); - const detector = urlParams.get('detector'); - const detectorName = detectors.getDetectorName(detector); - const flagsData = flags.getFlags(run, detectorName); - const runData = runs.getRun(dataPassName, run); - - const functionalities = (model) => h('.btn-group', - h('button.btn.btn-secondary.icon-only-button', { - onclick: () => { - model.fetchedData.reqForData(true); - model.notify(); - }, - }, iconReload()), - - h('button.btn.btn-secondary.icon-only-button', { - onclick: () => { - downloadCSV(model); - }, - }, iconDataTransferDownload()), - - h('button.btn.btn-secondary.icon-only-button', { - onclick: () => { - navigator.clipboard.writeText(model.router.getUrl().toString()) - .then(() => { - }) - .catch(() => { - }); - }, - }, iconShareBoxed()), - - h('button.btn.icon-only-button', { - className: model.searchFieldsVisible ? 'btn-primary' : 'btn-secondary', - onclick: () => model.changeSearchFieldsVisibility(), - }, model.searchFieldsVisible ? h('.slider-20-off-white.abs-center') : h('.slider-20-primary.abs-center'))); - - return run > defaultRunNumbers && runData - ? h('div.main-content', [ - h('div.flex-wrap.justify-between.items-center', - h('div.flex-wrap.justify-between.items-center', - flagBreadCrumbs(model, dataPassName, run, detectorName), - h('button.btn.btn-secondary', { - onclick: () => { - document.getElementById('pageSettingsModal').style.display = 'block'; - document.addEventListener('click', (event) => { - const modalContent = document.getElementsByClassName('modal-content'); - const modal = document.getElementsByClassName('modal'); - if (Array.from(modalContent).find((e) => e != event.target) - && Array.from(modal).find((e) => e == event.target) - && document.getElementById('pageSettingsModal')) { - document.getElementById('pageSettingsModal').style.display = 'none'; - } - }); - }, - }, h('.settings-20'))), - - h('div', functionalities(model))), - model.searchFieldsVisible ? filter(model) : '', - - flagsVisualization(runData, flagsData), - flagsTable(model, flagsData), - h('.modal', { id: 'pageSettingsModal' }, - h('.modal-content.abs-center.p3', { - id: 'pageSettingsModalContent', - }, pageSettings(model, () => { - document.getElementById('pageSettingsModal').style.display = 'none'; - }))), - ]) + return run > defaultRunNumbers + ? flagsContent(model, runs, detectors, flags) : noSubPageSelected(model); } diff --git a/app/public/views/runs/Runs.js b/app/public/views/runs/Runs.js index 87654ae2b..2652f723b 100644 --- a/app/public/views/runs/Runs.js +++ b/app/public/views/runs/Runs.js @@ -11,7 +11,7 @@ * or submit itself to any jurisdiction. */ -import { Observable, RemoteData } from '/js/src/index.js'; +import { Observable } from '/js/src/index.js'; import { RCT } from '../../config.js'; const { pageNames: PN } = RCT; const { dataReqParams: DRP } = RCT; @@ -42,7 +42,6 @@ export default class Runs extends Observable { async fetchRunsPerDataPass(dataPass) { const submodel = this.model.submodels[this.model.mode]; - this._runsPerDataPass = RemoteData.NotAsked(); const page = submodel.fetchedData[PN.dataPasses]; const [pIndex] = Object.keys(page); const { url } = page[pIndex].payload; From e467f81732eb82e1bfdd826b9c757df0d216af3b Mon Sep 17 00:00:00 2001 From: Ehevi Date: Fri, 14 Jul 2023 15:58:30 +0200 Subject: [PATCH 08/10] Fix flags navigation issue --- app/public/model/PrimaryModel.js | 75 ++++++++++++-------------------- 1 file changed, 29 insertions(+), 46 deletions(-) diff --git a/app/public/model/PrimaryModel.js b/app/public/model/PrimaryModel.js index 10fa0ed99..a0d63f9c3 100644 --- a/app/public/model/PrimaryModel.js +++ b/app/public/model/PrimaryModel.js @@ -50,12 +50,13 @@ export default class PrimaryModel extends Observable { async handleLocationChange() { const url = this.router.getUrl(); + const { page } = this.router.params; switch (url.pathname) { case '/': { - const { page } = this.router.params; if (! page) { this.router.go(`/?page=${pageNames.periods}&${dataReqParams.rowsOnSite}=50&${dataReqParams.site}=1&sorting=-name`); } else { + await this.pageNavigation(url, page); this.fetchedData.reqForData() .then(() => {}) .catch(() => {}); @@ -67,52 +68,34 @@ export default class PrimaryModel extends Observable { default: break; } + } - /* - * - *Switch (url.pathname) { - * case '/': - * const page = this.router.params['page']; - * if (! page) { - * this.router.go(`/?page=${pageNames.periods}&${dataReqParams.rowsOnSite}=50&${dataReqParams.site}=1&sorting=-name`); - * } else { - * switch (page) { - * case pageNames.flags: - * const dataPassName = this.router.params['data_pass_name']; - * console.log(dataPassName); - * if (dataPassName) { - * /* - * console.log(dataPassName); - * await this.parent.runs.fetchRunsPerDataPass(dataPassName).then(() => {}).catch((e) => {console.log(e)}); - * console.log(this.fetchedData); - * // console.log(this.fetchedData); - * - * const dpSearchParams = `?page=${pageNames.runsPerDataPass}&index=${dataPassName}&${dataReqParams.rowsOnSite}=50&${dataReqParams.site}=1`; - * const dpUrl = new URL(url.origin + url.pathname + dpSearchParams); - * this.fetchedData.reqForData(true, dpUrl).then(() => { - * const runNumbers = this.fetchedData[pageNames.runsPerDataPass][dataPassName].payload.rows.map((row) => row.run_number); - * this.parent.runs.fetchFlagsSummary(dataPassName, runNumbers).then(() => { - * this.fetchedData.reqForData(); - * }).catch(() => {}); - * }); - * - * } else this.goToDefaultPageUrl(pageNames.flags); - * break; - * default: - * this.fetchedData.reqForData() - * .then(() => {}) - * .catch(() => {}); - * break; - * } - * } - * break; - * case '/admin/': - * throw 'TODO'; - * default: - * break; - *} - * - */ + async pageNavigation(url, page) { + switch (page) { + case pageNames.flags: { + const dataPassName = this.router.params['data_pass_name']; + if (dataPassName) { + await this.parent.runs.fetchRunsPerDataPass(dataPassName).then(() => {}).catch((e) => {console.log(e)}); + + const dpSearchParams = `?page=${pageNames.runsPerDataPass}&index=${dataPassName}&${dataReqParams.rowsOnSite}=50&${dataReqParams.site}=1`; + const dpUrl = new URL(url.origin + url.pathname + dpSearchParams); + this.fetchedData.reqForData(true, dpUrl).then(() => { + const runNumbers = this.fetchedData[pageNames.runsPerDataPass][dataPassName].payload.rows.map((row) => row.run_number); + this.parent.runs.fetchFlagsSummary(dataPassName, runNumbers).then(() => { + this.fetchedData.reqForData(); + }).catch(() => {}); + }); + + } else this.goToDefaultPageUrl(pageNames.flags); + break; + } + default: { + this.fetchedData.reqForData() + .then(() => {}) + .catch(() => {}); + break; + } + } } async logout() { From 0d895919832868208f6f3636c65d4238de6ee3e1 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Fri, 14 Jul 2023 16:00:53 +0200 Subject: [PATCH 09/10] Apply eslint rules --- app/public/model/PrimaryModel.js | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/app/public/model/PrimaryModel.js b/app/public/model/PrimaryModel.js index a0d63f9c3..6276ea80e 100644 --- a/app/public/model/PrimaryModel.js +++ b/app/public/model/PrimaryModel.js @@ -75,24 +75,28 @@ export default class PrimaryModel extends Observable { case pageNames.flags: { const dataPassName = this.router.params['data_pass_name']; if (dataPassName) { - await this.parent.runs.fetchRunsPerDataPass(dataPassName).then(() => {}).catch((e) => {console.log(e)}); + await this.parent.runs.fetchRunsPerDataPass(dataPassName).then(() => {}).catch((e) => { + alert(e); + }); - const dpSearchParams = `?page=${pageNames.runsPerDataPass}&index=${dataPassName}&${dataReqParams.rowsOnSite}=50&${dataReqParams.site}=1`; - const dpUrl = new URL(url.origin + url.pathname + dpSearchParams); + const dpSearchParams = `?page=${pageNames.runsPerDataPass}&index=${dataPassName}`; + const siteReqParams = `&${dataReqParams.rowsOnSite}=50&${dataReqParams.site}=1`; + const dpUrl = new URL(url.origin + url.pathname + dpSearchParams + siteReqParams); this.fetchedData.reqForData(true, dpUrl).then(() => { - const runNumbers = this.fetchedData[pageNames.runsPerDataPass][dataPassName].payload.rows.map((row) => row.run_number); - this.parent.runs.fetchFlagsSummary(dataPassName, runNumbers).then(() => { - this.fetchedData.reqForData(); - }).catch(() => {}); - }); - - } else this.goToDefaultPageUrl(pageNames.flags); + const runNumbers = this.fetchedData[pageNames.runsPerDataPass][dataPassName].payload.rows.map((row) => row.run_number); + this.parent.runs.fetchFlagsSummary(dataPassName, runNumbers).then(() => { + this.fetchedData.reqForData(); + }).catch(() => {}); + }); + } else { + this.goToDefaultPageUrl(pageNames.flags); + } break; } default: { this.fetchedData.reqForData() - .then(() => {}) - .catch(() => {}); + .then(() => {}) + .catch(() => {}); break; } } From db7bdc6ff4f6fb2f6e088ec29a13b37462c1b474 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Fri, 14 Jul 2023 16:02:14 +0200 Subject: [PATCH 10/10] Cleanup :broom: --- app/public/model/PrimaryModel.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/public/model/PrimaryModel.js b/app/public/model/PrimaryModel.js index 6276ea80e..c7b0044de 100644 --- a/app/public/model/PrimaryModel.js +++ b/app/public/model/PrimaryModel.js @@ -75,9 +75,7 @@ export default class PrimaryModel extends Observable { case pageNames.flags: { const dataPassName = this.router.params['data_pass_name']; if (dataPassName) { - await this.parent.runs.fetchRunsPerDataPass(dataPassName).then(() => {}).catch((e) => { - alert(e); - }); + await this.parent.runs.fetchRunsPerDataPass(dataPassName).then(() => {}).catch(() => {}); const dpSearchParams = `?page=${pageNames.runsPerDataPass}&index=${dataPassName}`; const siteReqParams = `&${dataReqParams.rowsOnSite}=50&${dataReqParams.site}=1`;