From 8216274691badf454729806ecc6d38d639a3f978 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Mon, 14 Aug 2023 16:54:00 +0200 Subject: [PATCH 01/82] New api periods --- app/public/Model.js | 22 +- .../table/pagination/PaginationModel.js | 256 ++++++++++++++++++ app/public/utils/fetch/getRemoteData.js | 22 ++ app/public/utils/fetch/getRemoteDataSlice.js | 46 ++++ app/public/utils/fetch/jsonFetch.js | 57 ++++ app/public/views/dataAccessPanel.js | 2 +- app/public/views/periods/Periods.js | 117 ++++++++ .../views/periods/overview/periodsContent.js | 85 ++++++ .../views/periods/overview/periodsPanel.js | 28 ++ app/public/views/userView/data/dataPanel.js | 23 +- .../views/userView/data/table/tablePanel.js | 2 +- 11 files changed, 647 insertions(+), 13 deletions(-) create mode 100644 app/public/components/table/pagination/PaginationModel.js create mode 100644 app/public/utils/fetch/getRemoteData.js create mode 100644 app/public/utils/fetch/getRemoteDataSlice.js create mode 100644 app/public/utils/fetch/jsonFetch.js create mode 100644 app/public/views/periods/Periods.js create mode 100644 app/public/views/periods/overview/periodsContent.js create mode 100644 app/public/views/periods/overview/periodsPanel.js diff --git a/app/public/Model.js b/app/public/Model.js index 52cb43d0e..129b6f763 100644 --- a/app/public/Model.js +++ b/app/public/Model.js @@ -18,7 +18,8 @@ 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, dataAccess } = RCT; +import PeriodsModel from './views/periods/Periods.js'; +const { roles, dataAccess, pageNames } = RCT; export default class Model extends Observable { constructor() { @@ -34,6 +35,9 @@ export default class Model extends Observable { this.loader = new Loader(); + this.periods = new PeriodsModel(this); + this.periods.bubbleTo(this); + this.runs = new Runs(this); this.runs.bubbleTo(this); @@ -49,6 +53,22 @@ export default class Model extends Observable { this.loginEndpoint = `/api${RCT.endpoints.login}`; this.login('physicist'); + + this.handleLocationChange(); + } + + /** + * Delegates sub-model actions depending on new location of the page + * @returns {vnode} The page to be loaded + */ + async handleLocationChange() { + switch (this.router.params.page) { + case pageNames.periods: + await this.periods.fetchAllPeriods(); + break; + default: + break; + } } async login(username) { diff --git a/app/public/components/table/pagination/PaginationModel.js b/app/public/components/table/pagination/PaginationModel.js new file mode 100644 index 000000000..a304dc78f --- /dev/null +++ b/app/public/components/table/pagination/PaginationModel.js @@ -0,0 +1,256 @@ +/** + * @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 } from '/js/src/index.js'; + +const DEFAULT_ITEMS_PER_PAGE = 10; +const DEFAULT_CURRENT_PAGE = 1; +const DEFAULT_ITEMS_COUNT = 0; +const ENABLE_INFINITE_MODE_BY_DEFAULT = false; + +const INFINITE_SCROLL_CHUNK_SIZE = 19; + +/** + * Model to handle pagination + */ +export class PaginationModel extends Observable { + /** + * Constructor + */ + constructor() { + super(); + + // Fallback value that will be used if items per page is not defined + this._defaultItemsPerPage = null; + this._itemsPerPage = null; + this._customItemsPerPage = ''; + this._currentPage = DEFAULT_CURRENT_PAGE; + this._itemsCount = DEFAULT_ITEMS_COUNT; + this._isInfiniteScrollEnabled = ENABLE_INFINITE_MODE_BY_DEFAULT; + + this._itemsPerPageSelector$ = new Observable(); + } + + /** + * Reset the pagination to its default values + * + * @return {void} + */ + reset() { + this._itemsPerPage = null; + this._defaultItemsPerPage = null; + this._customItemsPerPage = ''; + this._currentPage = DEFAULT_CURRENT_PAGE; + this._isInfiniteScrollEnabled = ENABLE_INFINITE_MODE_BY_DEFAULT; + } + + /** + * Define the default items per page to use if there is not one already defined + * + * @param {number} defaultItemsPerPage the value to use as default items per page if none has been defined yet + * + * @return {void} + */ + provideDefaultItemsPerPage(defaultItemsPerPage) { + if (defaultItemsPerPage !== this._defaultItemsPerPage) { + this._defaultItemsPerPage = defaultItemsPerPage; + this.notify(); + } + } + + /** + * Defines the current page without notifying observers + * + * @param {number} page the current page + * @return {boolean} true if the page actually changed, else false + */ + silentlySetCurrentPage(page) { + if (this._currentPage !== page) { + this._currentPage = page; + return true; + } + return false; + } + + /** + * If the current page is not the last one, navigate to the next one + * + * @return {void} + */ + goToNextPage() { + if (this._currentPage < this.pagesCount) { + this.currentPage = this.currentPage + 1; + } + } + + /** + * Returns the current page + * + * @return {number} the current page + */ + get currentPage() { + return this._currentPage; + } + + /** + * Defines the current page and notify the change to observers + * + * @param {number} page the current page + */ + set currentPage(page) { + if (this.silentlySetCurrentPage(page)) { + this.notify(); + } + } + + /** + * Returns the amount of items displayed per page + * + * @return {number} the amount of items per page + */ + get itemsPerPage() { + return this.isInfiniteScrollEnabled + ? INFINITE_SCROLL_CHUNK_SIZE + : this._itemsPerPage || this._defaultItemsPerPage || DEFAULT_ITEMS_PER_PAGE; + } + + /** + * Defines the amount of items displayed per page + * + * @param {number} amount the amount of items + */ + set itemsPerPage(amount) { + if (this._isInfiniteScrollEnabled || this._itemsPerPage !== amount) { + this._itemsPerPage = amount; + this._currentPage = DEFAULT_CURRENT_PAGE; + } + this._isAmountDropdownVisible = false; + this._isInfiniteScrollEnabled = false; + + this.notify(); + } + + /** + * Returns the offset of the first item of the current page + * + * @return {number} the first item offset + */ + get firstItemOffset() { + return (this._currentPage - 1) * this.itemsPerPage; + } + + /** + * Returns the amount of items per page defined by the user as free choice + * + * @return {string} the custom items per page + */ + get customItemsPerPage() { + return this._customItemsPerPage; + } + + /** + * Defines the amount of items per page defined by the user as free choice + * + * @param {string} customItemsPerPage the custom items per page + * + * @return {void} + */ + set customItemsPerPage(customItemsPerPage) { + this._customItemsPerPage = customItemsPerPage; + this._itemsPerPageSelector$.notify(); + } + + /** + * Returns the total amount of pages available + * + * @return {number} the available pages count + */ + get pagesCount() { + return Math.ceil(this.itemsCount / this.itemsPerPage); + } + + /** + * Returns the total amount of items paginated + * + * @return {number} the amount of items + */ + get itemsCount() { + return this._itemsCount; + } + + /** + * Define the total amount of items paginated + * + * @param {number} itemsCount the amount of items + */ + set itemsCount(itemsCount) { + this._itemsCount = itemsCount; + } + + /** + * States if the infinite scroll mode is enabled + * + * @return {boolean} true if infinite scroll mode is enabled + */ + get isInfiniteScrollEnabled() { + return this._isInfiniteScrollEnabled; + } + + /** + * Enable the infinite mode + * + * @return {void} + */ + enableInfiniteMode() { + this._isInfiniteScrollEnabled = true; + this._isAmountDropdownVisible = false; + this._currentPage = DEFAULT_CURRENT_PAGE; + this.notify(); + } + + /** + * Toggles amount dropdown visibility + * + * @return {void} + */ + toggleAmountDropdownVisibility() { + this.isAmountDropdownVisible = !this.isAmountDropdownVisible; + } + + /** + * States if the amount dropdown is visible or not + * + * @return {boolean} true if the dropdown is visible + */ + get isAmountDropdownVisible() { + return this._isAmountDropdownVisible; + } + + /** + * Defines if the amount dropdown is visible + * + * @param {boolean} visible true to display the dropdown, else false + */ + set isAmountDropdownVisible(visible) { + this._isAmountDropdownVisible = visible; + this._itemsPerPageSelector$.notify(); + } + + /** + * Observable notified when the item per page selector change (either a custom value is typed, or its visibility change) + * + * @return {Observable} the selector observable + */ + get itemsPerPageSelector$() { + return this._itemsPerPageSelector$; + } +} diff --git a/app/public/utils/fetch/getRemoteData.js b/app/public/utils/fetch/getRemoteData.js new file mode 100644 index 000000000..7fc31da0f --- /dev/null +++ b/app/public/utils/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/utils/fetch/getRemoteDataSlice.js b/app/public/utils/fetch/getRemoteDataSlice.js new file mode 100644 index 000000000..5053e3a66 --- /dev/null +++ b/app/public/utils/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/utils/fetch/jsonFetch.js b/app/public/utils/fetch/jsonFetch.js new file mode 100644 index 000000000..b9bbcab7d --- /dev/null +++ b/app/public/utils/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/dataAccessPanel.js b/app/public/views/dataAccessPanel.js index fe1e5dfe4..519b5df7e 100644 --- a/app/public/views/dataAccessPanel.js +++ b/app/public/views/dataAccessPanel.js @@ -25,7 +25,7 @@ export default function dataAccessPanel(model) { h('section.flex-grow.relative.user-panel-main-content', [ h('.scroll-y.absolute-fill', { id: 'user-panel-main-content' }, - dataPanel(dataAccess, runs, detectors, flags)), + dataPanel(dataAccess, runs, detectors, flags, model)), ]), ]), ]); diff --git a/app/public/views/periods/Periods.js b/app/public/views/periods/Periods.js new file mode 100644 index 000000000..fdddc7a98 --- /dev/null +++ b/app/public/views/periods/Periods.js @@ -0,0 +1,117 @@ +/** + * @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 { PaginationModel } from '../../components/table/pagination/PaginationModel.js'; +import { getRemoteDataSlice } from '../../utils/fetch/getRemoteDataSlice.js'; + +/** + * Model representing handlers for periods page + * + * @implements {OverviewModel} + */ +export default class PeriodsModel extends Observable { + /** + * The constructor of the Overview model object + * @param {Model} model Pass the model to access the defined functions + * @returns {Object} Constructs the Overview model + */ + constructor(model) { + super(); + this.model = model; + + this._pagination = new PaginationModel(); + this._pagination.observe(() => this.fetchAllPeriods()); + this._pagination.itemsPerPageSelector$.observe(() => this.notify()); + + // Content + // this._currentPagePeriods = RemoteData.notAsked(); + // this._allPeriods = RemoteData.notAsked(); + + this._periods = RemoteData.NotAsked() + } + + /** + * Reset this model to its default + * + * @returns {void} + */ + reset() { + this._periods = RemoteData.NotAsked(); + this._pagination.reset(); + } + + /** + * Fetch all the relevant periods from the API + * + * @return {Promise} void + */ + async fetchAllPeriods() { + /** + * @type {Period[]} + */ + + /* + * When fetching data, to avoid concurrency issues, save a flag stating if the fetched data should be concatenated with the current one + * (infinite scroll) or if they should replace them + */ + + const shouldKeepExisting = this._pagination.currentPage > 1 && this._pagination.isInfiniteScrollEnabled; + + if (!this._pagination.isInfiniteScrollEnabled) { + this._currentPagePeriods = RemoteData.loading(); + this.notify(); + } + + /* + const params = { + 'page[offset]': this._pagination.firstItemOffset, + 'page[limit]': this._pagination.itemsPerPage, + }; + */ + + this._allPeriods = RemoteData.notAsked(); + + // const endpoint = `/api/periods?${new URLSearchParams(params).toString()}`; + const endpoint = '/api/periods'; + try { + const { items, totalCount } = await getRemoteDataSlice(endpoint); + const concatenateWith = shouldKeepExisting ? this._periods.payload || [] : []; + this._periods = RemoteData.success([...concatenateWith, ...items]); + this._pagination.itemsCount = totalCount; + } catch (errors) { + this._periods = RemoteData.failure(errors); + } + + this.notify(); + } + + /** + * Getter for all the run data + * @returns {RemoteData} Returns all of the filtered periods + */ + getPeriods() { + return this._allPeriods; + } + + /** + * Retro-compatibility access to paginated runs, returning all the runs contained in the current page if it applies + * + * @return {RemoteData} the runs in the current page + */ + get periods() { + return this._periods; + } + + +} \ No newline at end of file diff --git a/app/public/views/periods/overview/periodsContent.js b/app/public/views/periods/overview/periodsContent.js new file mode 100644 index 000000000..a0635e3fa --- /dev/null +++ b/app/public/views/periods/overview/periodsContent.js @@ -0,0 +1,85 @@ +/** + * @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 pagesCellsSpecials from '../../userView/data/pagesCellsSpecials.js'; +import { RCT } from '../../../config.js'; +import title from '../../../components/table/title.js'; +import dataActionButtons, { dataActions } from '../../../components/buttons/dataActionButtons.js'; +import filter from '../../userView/data/table/filtering/filter.js'; +import { anyFiltersActive } from '../../../utils/filtering/filterUtils.js'; +import activeFilters from '../../userView/data/table/filtering/activeFilters.js'; +const pageName = RCT.pageNames.periods; + +/** + * Build components in case of runs retrieval success + * @param {Model} model model to access global functions + * @param {RemoteData} runs list of runs retrieved from server + * @return {vnode[]} Returns a vnode with the table containing the runs + */ + +const applicableDataActions = { + [dataActions.hide]: true, + [dataActions.reload]: true, + [dataActions.downloadCSV]: true, + [dataActions.copyLink]: true, + [dataActions.showFilteringPanel]: true, +}; + +export default function periodsContent(periods, model) { + const table = (periods) => periods.map((period) => h('', period.id)); + + const cellsSpecials = pagesCellsSpecials[pageName]; + const url = model.router.getUrl(); + + return h('.p-1rem', + h('.flex-wrap.justify-between.items-center', + h('.flex-wrap.justify-between.items-center', + title(pageName)), + dataActionButtons(model, applicableDataActions)), + + model.showFilteringPanel ? filter(model) : '', + anyFiltersActive(url) ? activeFilters(model, url) : '', + + // periods.length > 0 + table(periods)); + + /* + return h('.p-1rem', [ + h('.flex-wrap.justify-between.items-center', + h('.flex-wrap.justify-between.items-center', + title(dataPointer.page)), + + dataActionButtons(model, applicableDataActions)), + model.showFilteringPanel ? filter(model) : '', + anyFiltersActive(url) ? activeFilters(model, url) : '', + + data.rows?.length > 0 + ? visibleFields.length > 0 + ? h('.p-top-05em', + h('.x-scrollable-table.border-sh', + pager(model, data, false), + h(`table.${dataPointer.page}-table`, { + id: `data-table-${data.url}`, + }, + tableHeader(visibleFields, data, model), + model.sortingRowVisible ? sortingRow(visibleFields, data, model) : '', + tableBody(model, visibleFields, data, cellsSpecials, runs)), + data.rows.length > 15 ? pager(model, data) : '')) + : '' + : anyFiltersActive(url) + ? noMatchingData(model, dataPointer.page) + : noDataFound(model), + ]); + */ +} diff --git a/app/public/views/periods/overview/periodsPanel.js b/app/public/views/periods/overview/periodsPanel.js new file mode 100644 index 000000000..4049d3d84 --- /dev/null +++ b/app/public/views/periods/overview/periodsPanel.js @@ -0,0 +1,28 @@ +/** + * @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 spinner from "../../../components/common/spinner.js"; +import periodsContent from "./periodsContent.js"; + +export default function periodsPanel(model) { + const { periods } = model.periods; + + return periods.match({ + NotAsked: () => 'not asked', + Loading: () => h('tr', + h('td', spinner())), + Success: () => periodsContent(periods.payload, model), + Failure: (errors) => h('tr', h('td', { colSpan: columnsCount }, errors)) + }) +} diff --git a/app/public/views/userView/data/dataPanel.js b/app/public/views/userView/data/dataPanel.js index 0db63b15f..c5f18855b 100644 --- a/app/public/views/userView/data/dataPanel.js +++ b/app/public/views/userView/data/dataPanel.js @@ -18,6 +18,7 @@ import { default as runsPerDataPassPanel } from '../../runs/runsPerDataPass/over import { default as runsPerPeriodPanel } from '../../runs/runsPerPeriod/overview/panel.js'; import { failure, unknown, waiting } from '../../../components/messagePanel/messages.js'; import { RCT } from '../../../config.js'; +import periodsPanel from '../../periods/overview/periodsPanel.js'; const { pageNames } = RCT; /** @@ -26,25 +27,27 @@ const { pageNames } = RCT; * @returns {*} */ -export default function dataPanel(model, runs, detectors, flags) { - const { page, index } = model.getCurrentDataPointer(); - const data = model.fetchedData[page][index]; +export default function dataPanel(dataAccess, runs, detectors, flags, model) { + const { page, index } = dataAccess.getCurrentDataPointer(); + const data = dataAccess.fetchedData[page][index]; return data ? data.match({ - NotAsked: () => unknown(model), + NotAsked: () => unknown(dataAccess), Loading: () => waiting(), Success: () => { switch (page) { + case pageNames.periods: + return periodsPanel(model); case pageNames.flags: - return flagsPanel(model, runs, detectors, flags); + return flagsPanel(dataAccess, runs, detectors, flags); case pageNames.runsPerDataPass: - return runsPerDataPassPanel(model, runs, detectors); + return runsPerDataPassPanel(dataAccess, runs, detectors); case pageNames.runsPerPeriod: - return runsPerPeriodPanel(model, runs, detectors); + return runsPerPeriodPanel(dataAccess, runs, detectors); default: - return tablePanel(model, runs); + return tablePanel(dataAccess, runs); } }, - Failure: (status) => failure(model, status), - }) : unknown(model); + Failure: (status) => failure(dataAccess, status), + }) : unknown(dataAccess); } diff --git a/app/public/views/userView/data/table/tablePanel.js b/app/public/views/userView/data/table/tablePanel.js index 48ec3380a..ef19327be 100644 --- a/app/public/views/userView/data/table/tablePanel.js +++ b/app/public/views/userView/data/table/tablePanel.js @@ -59,7 +59,7 @@ export default function tablePanel(model, runs) { data.rows = data.rows.filter((item) => item.name != 'null'); - const cellsSpecials = pagesCellsSpecials[dataPointer.page]; + const cellsSpecials = pagesCellsSpecials[dataPointer.page]; // const { fields } = data; const visibleFields = fields.filter((f) => f.marked); From 073fef1f77b00b0e1611e3d4ec801b060a23ed3d Mon Sep 17 00:00:00 2001 From: Ehevi Date: Tue, 15 Aug 2023 10:39:00 +0200 Subject: [PATCH 02/82] Apply eslint rules :sparkles: --- app/public/views/periods/Periods.js | 30 ++++---- .../views/periods/overview/periodsContent.js | 70 +++++++++---------- .../views/periods/overview/periodsPanel.js | 11 +-- 3 files changed, 56 insertions(+), 55 deletions(-) diff --git a/app/public/views/periods/Periods.js b/app/public/views/periods/Periods.js index fdddc7a98..eeb51fc6e 100644 --- a/app/public/views/periods/Periods.js +++ b/app/public/views/periods/Periods.js @@ -34,11 +34,13 @@ export default class PeriodsModel extends Observable { this._pagination.observe(() => this.fetchAllPeriods()); this._pagination.itemsPerPageSelector$.observe(() => this.notify()); - // Content - // this._currentPagePeriods = RemoteData.notAsked(); - // this._allPeriods = RemoteData.notAsked(); + /* + * Content + * this._currentPagePeriods = RemoteData.notAsked(); + * this._allPeriods = RemoteData.notAsked(); + */ - this._periods = RemoteData.NotAsked() + this._periods = RemoteData.NotAsked(); } /** @@ -60,12 +62,12 @@ export default class PeriodsModel extends Observable { /** * @type {Period[]} */ - + /* * When fetching data, to avoid concurrency issues, save a flag stating if the fetched data should be concatenated with the current one * (infinite scroll) or if they should replace them */ - + const shouldKeepExisting = this._pagination.currentPage > 1 && this._pagination.isInfiniteScrollEnabled; if (!this._pagination.isInfiniteScrollEnabled) { @@ -74,15 +76,15 @@ export default class PeriodsModel extends Observable { } /* - const params = { - 'page[offset]': this._pagination.firstItemOffset, - 'page[limit]': this._pagination.itemsPerPage, - }; - */ + *Const params = { + * 'page[offset]': this._pagination.firstItemOffset, + * 'page[limit]': this._pagination.itemsPerPage, + *}; + */ this._allPeriods = RemoteData.notAsked(); - // const endpoint = `/api/periods?${new URLSearchParams(params).toString()}`; + // Const endpoint = `/api/periods?${new URLSearchParams(params).toString()}`; const endpoint = '/api/periods'; try { const { items, totalCount } = await getRemoteDataSlice(endpoint); @@ -112,6 +114,4 @@ export default class PeriodsModel extends Observable { get periods() { return this._periods; } - - -} \ No newline at end of file +} diff --git a/app/public/views/periods/overview/periodsContent.js b/app/public/views/periods/overview/periodsContent.js index a0635e3fa..2ea46dd57 100644 --- a/app/public/views/periods/overview/periodsContent.js +++ b/app/public/views/periods/overview/periodsContent.js @@ -12,7 +12,7 @@ */ import { h } from '/js/src/index.js'; -import pagesCellsSpecials from '../../userView/data/pagesCellsSpecials.js'; +// Import pagesCellsSpecials from '../../userView/data/pagesCellsSpecials.js'; import { RCT } from '../../../config.js'; import title from '../../../components/table/title.js'; import dataActionButtons, { dataActions } from '../../../components/buttons/dataActionButtons.js'; @@ -39,47 +39,47 @@ const applicableDataActions = { export default function periodsContent(periods, model) { const table = (periods) => periods.map((period) => h('', period.id)); - const cellsSpecials = pagesCellsSpecials[pageName]; + // Const cellsSpecials = pagesCellsSpecials[pageName]; const url = model.router.getUrl(); - return h('.p-1rem', + return h('.p-1rem', h('.flex-wrap.justify-between.items-center', - h('.flex-wrap.justify-between.items-center', - title(pageName)), - dataActionButtons(model, applicableDataActions)), - + h('.flex-wrap.justify-between.items-center', + title(pageName)), + dataActionButtons(model, applicableDataActions)), + model.showFilteringPanel ? filter(model) : '', anyFiltersActive(url) ? activeFilters(model, url) : '', - // periods.length > 0 + // Periods.length > 0 table(periods)); /* - return h('.p-1rem', [ - h('.flex-wrap.justify-between.items-center', - h('.flex-wrap.justify-between.items-center', - title(dataPointer.page)), - - dataActionButtons(model, applicableDataActions)), - model.showFilteringPanel ? filter(model) : '', - anyFiltersActive(url) ? activeFilters(model, url) : '', - - data.rows?.length > 0 - ? visibleFields.length > 0 - ? h('.p-top-05em', - h('.x-scrollable-table.border-sh', - pager(model, data, false), - h(`table.${dataPointer.page}-table`, { - id: `data-table-${data.url}`, - }, - tableHeader(visibleFields, data, model), - model.sortingRowVisible ? sortingRow(visibleFields, data, model) : '', - tableBody(model, visibleFields, data, cellsSpecials, runs)), - data.rows.length > 15 ? pager(model, data) : '')) - : '' - : anyFiltersActive(url) - ? noMatchingData(model, dataPointer.page) - : noDataFound(model), - ]); - */ + *Return h('.p-1rem', [ + * h('.flex-wrap.justify-between.items-center', + * h('.flex-wrap.justify-between.items-center', + * title(dataPointer.page)), + * + * dataActionButtons(model, applicableDataActions)), + * model.showFilteringPanel ? filter(model) : '', + * anyFiltersActive(url) ? activeFilters(model, url) : '', + * + * data.rows?.length > 0 + * ? visibleFields.length > 0 + * ? h('.p-top-05em', + * h('.x-scrollable-table.border-sh', + * pager(model, data, false), + * h(`table.${dataPointer.page}-table`, { + * id: `data-table-${data.url}`, + * }, + * tableHeader(visibleFields, data, model), + * model.sortingRowVisible ? sortingRow(visibleFields, data, model) : '', + * tableBody(model, visibleFields, data, cellsSpecials, runs)), + * data.rows.length > 15 ? pager(model, data) : '')) + * : '' + * : anyFiltersActive(url) + * ? noMatchingData(model, dataPointer.page) + * : noDataFound(model), + *]); + */ } diff --git a/app/public/views/periods/overview/periodsPanel.js b/app/public/views/periods/overview/periodsPanel.js index 4049d3d84..646b26302 100644 --- a/app/public/views/periods/overview/periodsPanel.js +++ b/app/public/views/periods/overview/periodsPanel.js @@ -12,8 +12,9 @@ * or submit itself to any jurisdiction. */ -import spinner from "../../../components/common/spinner.js"; -import periodsContent from "./periodsContent.js"; +import { h } from '/js/src/index.js'; +import spinner from '../../../components/common/spinner.js'; +import periodsContent from './periodsContent.js'; export default function periodsPanel(model) { const { periods } = model.periods; @@ -21,8 +22,8 @@ export default function periodsPanel(model) { return periods.match({ NotAsked: () => 'not asked', Loading: () => h('tr', - h('td', spinner())), + h('td', spinner())), Success: () => periodsContent(periods.payload, model), - Failure: (errors) => h('tr', h('td', { colSpan: columnsCount }, errors)) - }) + Failure: (errors) => h('', errors), + }); } From f2b237b080d0268f4ca780b589e11a95fab7759a Mon Sep 17 00:00:00 2001 From: Ehevi Date: Tue, 15 Aug 2023 10:46:29 +0200 Subject: [PATCH 03/82] Use message panels --- app/public/views/periods/overview/periodsPanel.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app/public/views/periods/overview/periodsPanel.js b/app/public/views/periods/overview/periodsPanel.js index 646b26302..f44977b27 100644 --- a/app/public/views/periods/overview/periodsPanel.js +++ b/app/public/views/periods/overview/periodsPanel.js @@ -12,18 +12,17 @@ * or submit itself to any jurisdiction. */ -import { h } from '/js/src/index.js'; -import spinner from '../../../components/common/spinner.js'; import periodsContent from './periodsContent.js'; +import { waiting, unknown, failure } from '../../../components/messagePanel/messages.js'; export default function periodsPanel(model) { const { periods } = model.periods; + const { dataAccess } = model; return periods.match({ - NotAsked: () => 'not asked', - Loading: () => h('tr', - h('td', spinner())), + NotAsked: () => unknown(dataAccess), + Loading: () => waiting(), Success: () => periodsContent(periods.payload, model), - Failure: (errors) => h('', errors), + Failure: (errors) => failure(model, errors), }); } From c3df896d17f7db9894368c7d0f3abd469bd07de4 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Tue, 15 Aug 2023 11:09:07 +0200 Subject: [PATCH 04/82] Messages --- app/public/components/messagePanel/messages.js | 12 +++++++++++- app/public/views/periods/Periods.js | 16 ++++++++-------- .../views/periods/overview/periodsPanel.js | 4 ++-- app/public/views/userView/data/dataPanel.js | 4 ++-- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/app/public/components/messagePanel/messages.js b/app/public/components/messagePanel/messages.js index c89689df2..1f4d4e4dd 100644 --- a/app/public/components/messagePanel/messages.js +++ b/app/public/components/messagePanel/messages.js @@ -27,13 +27,23 @@ const removeCurrentDataButton = (model, label) => h('button.btn.btn-primary.m3', onclick: () => model.removeCurrentData(), }, label); -export const failure = (model, status) => messagePanel( +export const failureWithStatus = (model, status) => messagePanel( 'no-network-90', 'Failed to load data', `The services are unavailable (status: ${status ? status : 'unknown'})`, requestButton(model), ); +export const failureWithMessage = (model, errorObject) => { + const { detail, title } = errorObject.find((e) => Boolean(e)); + return messagePanel( + 'no-network-90', + detail, + title, + requestButton(model), + ); +}; + export const unknown = (model) => messagePanel( 'unexpected-90', 'Unknown error', diff --git a/app/public/views/periods/Periods.js b/app/public/views/periods/Periods.js index eeb51fc6e..ff30b91ee 100644 --- a/app/public/views/periods/Periods.js +++ b/app/public/views/periods/Periods.js @@ -34,6 +34,8 @@ export default class PeriodsModel extends Observable { this._pagination.observe(() => this.fetchAllPeriods()); this._pagination.itemsPerPageSelector$.observe(() => this.notify()); + this._visibleFields = null; + /* * Content * this._currentPagePeriods = RemoteData.notAsked(); @@ -50,6 +52,7 @@ export default class PeriodsModel extends Observable { */ reset() { this._periods = RemoteData.NotAsked(); + this._visibleFields = null; this._pagination.reset(); } @@ -75,17 +78,14 @@ export default class PeriodsModel extends Observable { this.notify(); } - /* - *Const params = { - * 'page[offset]': this._pagination.firstItemOffset, - * 'page[limit]': this._pagination.itemsPerPage, - *}; - */ + const params = { + 'page[offset]': this._pagination.firstItemOffset, + 'page[limit]': this._pagination.itemsPerPage, + }; this._allPeriods = RemoteData.notAsked(); - // Const endpoint = `/api/periods?${new URLSearchParams(params).toString()}`; - const endpoint = '/api/periods'; + const endpoint = `/api/periods?${new URLSearchParams(params).toString()}`; try { const { items, totalCount } = await getRemoteDataSlice(endpoint); const concatenateWith = shouldKeepExisting ? this._periods.payload || [] : []; diff --git a/app/public/views/periods/overview/periodsPanel.js b/app/public/views/periods/overview/periodsPanel.js index f44977b27..42ddd78d0 100644 --- a/app/public/views/periods/overview/periodsPanel.js +++ b/app/public/views/periods/overview/periodsPanel.js @@ -13,7 +13,7 @@ */ import periodsContent from './periodsContent.js'; -import { waiting, unknown, failure } from '../../../components/messagePanel/messages.js'; +import { waiting, unknown, failureWithMessage } from '../../../components/messagePanel/messages.js'; export default function periodsPanel(model) { const { periods } = model.periods; @@ -23,6 +23,6 @@ export default function periodsPanel(model) { NotAsked: () => unknown(dataAccess), Loading: () => waiting(), Success: () => periodsContent(periods.payload, model), - Failure: (errors) => failure(model, errors), + Failure: (errors) => failureWithMessage(model, errors), }); } diff --git a/app/public/views/userView/data/dataPanel.js b/app/public/views/userView/data/dataPanel.js index c5f18855b..11c578f79 100644 --- a/app/public/views/userView/data/dataPanel.js +++ b/app/public/views/userView/data/dataPanel.js @@ -16,7 +16,7 @@ import tablePanel from './table/tablePanel.js'; import flagsPanel from '../../flags/overview/flagsPanel.js'; import { default as runsPerDataPassPanel } from '../../runs/runsPerDataPass/overview/panel.js'; import { default as runsPerPeriodPanel } from '../../runs/runsPerPeriod/overview/panel.js'; -import { failure, unknown, waiting } from '../../../components/messagePanel/messages.js'; +import { failureWithStatus, unknown, waiting } from '../../../components/messagePanel/messages.js'; import { RCT } from '../../../config.js'; import periodsPanel from '../../periods/overview/periodsPanel.js'; const { pageNames } = RCT; @@ -48,6 +48,6 @@ export default function dataPanel(dataAccess, runs, detectors, flags, model) { return tablePanel(dataAccess, runs); } }, - Failure: (status) => failure(dataAccess, status), + Failure: (status) => failureWithStatus(dataAccess, status), }) : unknown(dataAccess); } From 2a9f8eb7cc3b4daf69891a79fee8f9edcab7a1cc Mon Sep 17 00:00:00 2001 From: Ehevi Date: Tue, 15 Aug 2023 11:41:01 +0200 Subject: [PATCH 05/82] Add dataAccess --- app/public/views/periods/Periods.js | 13 ++++++++----- app/public/views/periods/overview/periodsContent.js | 7 ++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/public/views/periods/Periods.js b/app/public/views/periods/Periods.js index ff30b91ee..a5caf4480 100644 --- a/app/public/views/periods/Periods.js +++ b/app/public/views/periods/Periods.js @@ -78,14 +78,17 @@ export default class PeriodsModel extends Observable { this.notify(); } - const params = { - 'page[offset]': this._pagination.firstItemOffset, - 'page[limit]': this._pagination.itemsPerPage, - }; + /* + *Const params = { + * 'page[offset]': this._pagination.firstItemOffset, + * 'page[limit]': this._pagination.itemsPerPage, + *}; + */ this._allPeriods = RemoteData.notAsked(); - const endpoint = `/api/periods?${new URLSearchParams(params).toString()}`; + const endpoint = '/api/periods'; + // Const endpoint = `/api/periods?${new URLSearchParams(params).toString()}`; try { const { items, totalCount } = await getRemoteDataSlice(endpoint); const concatenateWith = shouldKeepExisting ? this._periods.payload || [] : []; diff --git a/app/public/views/periods/overview/periodsContent.js b/app/public/views/periods/overview/periodsContent.js index 2ea46dd57..0d749b951 100644 --- a/app/public/views/periods/overview/periodsContent.js +++ b/app/public/views/periods/overview/periodsContent.js @@ -38,6 +38,7 @@ const applicableDataActions = { export default function periodsContent(periods, model) { const table = (periods) => periods.map((period) => h('', period.id)); + const { dataAccess } = model; // Const cellsSpecials = pagesCellsSpecials[pageName]; const url = model.router.getUrl(); @@ -46,10 +47,10 @@ export default function periodsContent(periods, model) { h('.flex-wrap.justify-between.items-center', h('.flex-wrap.justify-between.items-center', title(pageName)), - dataActionButtons(model, applicableDataActions)), + dataActionButtons(dataAccess, applicableDataActions)), - model.showFilteringPanel ? filter(model) : '', - anyFiltersActive(url) ? activeFilters(model, url) : '', + model.showFilteringPanel ? filter(dataAccess) : '', + anyFiltersActive(url) ? activeFilters(dataAccess, url) : '', // Periods.length > 0 table(periods)); From 00965aefbfe0f88b8468a53ed133fddbffadd171 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Tue, 15 Aug 2023 12:01:32 +0200 Subject: [PATCH 06/82] Move userPreferences up in model hierarchy --- app/public/Model.js | 4 ++++ app/public/model/DataAccessModel.js | 2 -- app/public/model/data/FetchedDataManager.js | 2 +- app/public/model/navigation/Navigation.js | 6 +++--- app/public/views/modal/modal.js | 4 ++-- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/app/public/Model.js b/app/public/Model.js index 129b6f763..3cf8a7325 100644 --- a/app/public/Model.js +++ b/app/public/Model.js @@ -19,6 +19,7 @@ import Flags from './views/flags/Flags.js'; import Detectors from './views/detectors/Detectors.js'; import Runs from './views/runs/Runs.js'; import PeriodsModel from './views/periods/Periods.js'; +import UserPreferences from './model/UserPreferences.js'; const { roles, dataAccess, pageNames } = RCT; export default class Model extends Observable { @@ -35,6 +36,9 @@ export default class Model extends Observable { this.loader = new Loader(); + this.userPreferences = new UserPreferences(this); + this.userPreferences.bubbleTo(this); + this.periods = new PeriodsModel(this); this.periods.bubbleTo(this); diff --git a/app/public/model/DataAccessModel.js b/app/public/model/DataAccessModel.js index f29663307..17050ee51 100644 --- a/app/public/model/DataAccessModel.js +++ b/app/public/model/DataAccessModel.js @@ -18,7 +18,6 @@ import { defaultIndex, defaultIndexString } from '../utils/defaults.js'; import Navigation from './navigation/Navigation.js'; import ServiceUnavailable from './ServiceUnavailable.js'; import { RCT } from '../config.js'; -import UserPreferences from './UserPreferences.js'; const { messageTimeout } = RCT; const { states } = RCT.dataAccess; @@ -29,7 +28,6 @@ export default class DataAccessModel extends Observable { this.parent = parent; this.router = this.parent.router; - this.userPreferences = new UserPreferences(this); this.serviceUnavailable = new ServiceUnavailable(parent); this.fetchedData = new FetchedDataManager(this.router, this); diff --git a/app/public/model/data/FetchedDataManager.js b/app/public/model/data/FetchedDataManager.js index 8bff8b0de..366e4ba80 100644 --- a/app/public/model/data/FetchedDataManager.js +++ b/app/public/model/data/FetchedDataManager.js @@ -99,7 +99,7 @@ export default class FetchedDataManager { await this.model.parent._tokenExpirationHandler(status); if (ok) { - const s = RemoteData.Success(new FetchedData(url, result, this.model.userPreferences, totalRecordsNumber)); + const s = RemoteData.Success(new FetchedData(url, result, this.model.parent.userPreferences, totalRecordsNumber)); this[page][index] = s; previous?.match({ NotAsked: () => {}, diff --git a/app/public/model/navigation/Navigation.js b/app/public/model/navigation/Navigation.js index 36bc7d902..61d505ee8 100644 --- a/app/public/model/navigation/Navigation.js +++ b/app/public/model/navigation/Navigation.js @@ -74,7 +74,7 @@ export default class Navigation extends Observable { this.parent.fetchedData.reqForData(true, dpUrl).then(() => { const runNumbers = this.model.runs.getRunsPerDataPass(dataPassName).map((row) => row.run_number); this.model.runs.fetchFlagsSummary(dataPassName, runNumbers).then(() => { - parent.fetchedData.reqForData(); + this.parent.fetchedData.reqForData(); }).catch(() => {}); }); } @@ -97,12 +97,12 @@ export default class Navigation extends Observable { } siteReqParamsPhrase() { - return `&${dataReqParams.rowsOnSite}=${this.parent.userPreferences.rowsOnSite}&${dataReqParams.site}=${this.site}`; + return `&${dataReqParams.rowsOnSite}=${this.model.userPreferences.rowsOnSite}&${dataReqParams.site}=${this.site}`; } siteReqParams() { return { - [dataReqParams.rowsOnSite]: this.parent.userPreferences.rowsOnSite, + [dataReqParams.rowsOnSite]: this.model.userPreferences.rowsOnSite, [dataReqParams.site]: this.site, }; } diff --git a/app/public/views/modal/modal.js b/app/public/views/modal/modal.js index d27a4bbfa..cc95018da 100644 --- a/app/public/views/modal/modal.js +++ b/app/public/views/modal/modal.js @@ -66,7 +66,7 @@ export const modal = (modalId, model = null) => { ? h(`.${modalClassNames.modal}`, { id: modalIds.pageSettings.modal }, h(`.${modalClassNames.content}.abs-center.p3`, { id: modalIds.pageSettings.content, - }, pageSettings(model.userPreferences, () => { + }, pageSettings(model.parent.userPreferences, () => { document.getElementById(modalIds.pageSettings.modal).style.display = 'none'; }))) : ''; @@ -81,7 +81,7 @@ export const modal = (modalId, model = null) => { return h(`.${modalClassNames.modal}`, { id: modalIds.detectors.modal }, h(`.${modalClassNames.content}.abs-center.p3`, { id: modalIds.detectors.content, - }, detectorSettings(model.userPreferences))); + }, detectorSettings(model.parent.userPreferences))); } } }; From f917829fbcc5c49d28a05653a2814e6a2cbd676d Mon Sep 17 00:00:00 2001 From: Ehevi Date: Tue, 15 Aug 2023 12:53:36 +0200 Subject: [PATCH 07/82] Eslint :sparkles: --- app/public/Model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/public/Model.js b/app/public/Model.js index 3cf8a7325..4c8ea4bf8 100644 --- a/app/public/Model.js +++ b/app/public/Model.js @@ -38,7 +38,7 @@ export default class Model extends Observable { this.userPreferences = new UserPreferences(this); this.userPreferences.bubbleTo(this); - + this.periods = new PeriodsModel(this); this.periods.bubbleTo(this); From 116ce04deece8fee58a9cd10c7a11e462b009345 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Tue, 15 Aug 2023 13:18:31 +0200 Subject: [PATCH 08/82] Cleanup :broom: --- .../table/pagination/PaginationModel.js | 91 ++----------------- app/public/views/periods/Periods.js | 16 ++-- 2 files changed, 13 insertions(+), 94 deletions(-) diff --git a/app/public/components/table/pagination/PaginationModel.js b/app/public/components/table/pagination/PaginationModel.js index a304dc78f..5c965d8dc 100644 --- a/app/public/components/table/pagination/PaginationModel.js +++ b/app/public/components/table/pagination/PaginationModel.js @@ -12,7 +12,6 @@ */ import { Observable } from '/js/src/index.js'; -const DEFAULT_ITEMS_PER_PAGE = 10; const DEFAULT_CURRENT_PAGE = 1; const DEFAULT_ITEMS_COUNT = 0; const ENABLE_INFINITE_MODE_BY_DEFAULT = false; @@ -25,19 +24,16 @@ const INFINITE_SCROLL_CHUNK_SIZE = 19; export class PaginationModel extends Observable { /** * Constructor + * @param {UserPreferences} userPreferences user preferences */ - constructor() { + constructor(userPreferences) { super(); - // Fallback value that will be used if items per page is not defined - this._defaultItemsPerPage = null; - this._itemsPerPage = null; - this._customItemsPerPage = ''; + this._userPreferences = userPreferences; + this._itemsPerPage = userPreferences.rowsOnSite; this._currentPage = DEFAULT_CURRENT_PAGE; this._itemsCount = DEFAULT_ITEMS_COUNT; this._isInfiniteScrollEnabled = ENABLE_INFINITE_MODE_BY_DEFAULT; - - this._itemsPerPageSelector$ = new Observable(); } /** @@ -46,27 +42,12 @@ export class PaginationModel extends Observable { * @return {void} */ reset() { - this._itemsPerPage = null; - this._defaultItemsPerPage = null; - this._customItemsPerPage = ''; + this._itemsPerPage = this._userPreferences.rowsOnSite; this._currentPage = DEFAULT_CURRENT_PAGE; + this._itemsCount = DEFAULT_ITEMS_COUNT; this._isInfiniteScrollEnabled = ENABLE_INFINITE_MODE_BY_DEFAULT; } - /** - * Define the default items per page to use if there is not one already defined - * - * @param {number} defaultItemsPerPage the value to use as default items per page if none has been defined yet - * - * @return {void} - */ - provideDefaultItemsPerPage(defaultItemsPerPage) { - if (defaultItemsPerPage !== this._defaultItemsPerPage) { - this._defaultItemsPerPage = defaultItemsPerPage; - this.notify(); - } - } - /** * Defines the current page without notifying observers * @@ -120,7 +101,7 @@ export class PaginationModel extends Observable { get itemsPerPage() { return this.isInfiniteScrollEnabled ? INFINITE_SCROLL_CHUNK_SIZE - : this._itemsPerPage || this._defaultItemsPerPage || DEFAULT_ITEMS_PER_PAGE; + : this._itemsPerPage; } /** @@ -148,27 +129,6 @@ export class PaginationModel extends Observable { return (this._currentPage - 1) * this.itemsPerPage; } - /** - * Returns the amount of items per page defined by the user as free choice - * - * @return {string} the custom items per page - */ - get customItemsPerPage() { - return this._customItemsPerPage; - } - - /** - * Defines the amount of items per page defined by the user as free choice - * - * @param {string} customItemsPerPage the custom items per page - * - * @return {void} - */ - set customItemsPerPage(customItemsPerPage) { - this._customItemsPerPage = customItemsPerPage; - this._itemsPerPageSelector$.notify(); - } - /** * Returns the total amount of pages available * @@ -216,41 +176,4 @@ export class PaginationModel extends Observable { this._currentPage = DEFAULT_CURRENT_PAGE; this.notify(); } - - /** - * Toggles amount dropdown visibility - * - * @return {void} - */ - toggleAmountDropdownVisibility() { - this.isAmountDropdownVisible = !this.isAmountDropdownVisible; - } - - /** - * States if the amount dropdown is visible or not - * - * @return {boolean} true if the dropdown is visible - */ - get isAmountDropdownVisible() { - return this._isAmountDropdownVisible; - } - - /** - * Defines if the amount dropdown is visible - * - * @param {boolean} visible true to display the dropdown, else false - */ - set isAmountDropdownVisible(visible) { - this._isAmountDropdownVisible = visible; - this._itemsPerPageSelector$.notify(); - } - - /** - * Observable notified when the item per page selector change (either a custom value is typed, or its visibility change) - * - * @return {Observable} the selector observable - */ - get itemsPerPageSelector$() { - return this._itemsPerPageSelector$; - } } diff --git a/app/public/views/periods/Periods.js b/app/public/views/periods/Periods.js index a5caf4480..85fcf3751 100644 --- a/app/public/views/periods/Periods.js +++ b/app/public/views/periods/Periods.js @@ -30,9 +30,8 @@ export default class PeriodsModel extends Observable { super(); this.model = model; - this._pagination = new PaginationModel(); + this._pagination = new PaginationModel(model.userPreferences); this._pagination.observe(() => this.fetchAllPeriods()); - this._pagination.itemsPerPageSelector$.observe(() => this.notify()); this._visibleFields = null; @@ -78,17 +77,14 @@ export default class PeriodsModel extends Observable { this.notify(); } - /* - *Const params = { - * 'page[offset]': this._pagination.firstItemOffset, - * 'page[limit]': this._pagination.itemsPerPage, - *}; - */ + const params = { + 'page[offset]': this._pagination.firstItemOffset, + 'page[limit]': this._pagination.itemsPerPage, + }; this._allPeriods = RemoteData.notAsked(); - const endpoint = '/api/periods'; - // Const endpoint = `/api/periods?${new URLSearchParams(params).toString()}`; + const endpoint = `/api/periods?${new URLSearchParams(params).toString()}`; try { const { items, totalCount } = await getRemoteDataSlice(endpoint); const concatenateWith = shouldKeepExisting ? this._periods.payload || [] : []; From 7beba233c93031365564bfc1114b931ea471c658 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Tue, 15 Aug 2023 14:09:40 +0200 Subject: [PATCH 09/82] Cleanup :broom: --- app/config/rct-data/viewFieldNames/index.js | 2 ++ .../viewFieldNames/oldPeriodFieldNames.js | 35 +++++++++++++++++++ .../viewFieldNames/periodFieldNames.js | 12 +++---- app/public/views/periods/Periods.js | 19 +++++++--- .../views/periods/overview/periodsContent.js | 8 +++-- 5 files changed, 63 insertions(+), 13 deletions(-) create mode 100644 app/config/rct-data/viewFieldNames/oldPeriodFieldNames.js diff --git a/app/config/rct-data/viewFieldNames/index.js b/app/config/rct-data/viewFieldNames/index.js index 41acc8f01..a038c2e9c 100644 --- a/app/config/rct-data/viewFieldNames/index.js +++ b/app/config/rct-data/viewFieldNames/index.js @@ -12,12 +12,14 @@ * or submit itself to any jurisdiction. */ +const oldPeriodFieldNames = require('./oldPeriodFieldNames.js'); const periodFieldNames = require('./periodFieldNames.js'); const runFieldNames = require('./runFieldNames.js'); const dataPassFieldNames = require('./dataPassFieldNames.js'); const mcFieldNames = require('./mcFieldNames.js'); module.exports = { + oldPeriodFieldNames, periodFieldNames, runFieldNames, dataPassFieldNames, diff --git a/app/config/rct-data/viewFieldNames/oldPeriodFieldNames.js b/app/config/rct-data/viewFieldNames/oldPeriodFieldNames.js new file mode 100644 index 000000000..86a5238d0 --- /dev/null +++ b/app/config/rct-data/viewFieldNames/oldPeriodFieldNames.js @@ -0,0 +1,35 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ +const { filterInputTypes } = require('../filterTypes.js'); + +const oldPeriodFieldNames = { + name: { + fieldName: 'Name', + filterInput: filterInputTypes.text, + }, + year: { + fieldName: 'Year', + filterInput: filterInputTypes.number, + }, + beam: { + fieldName: 'Beam', + filterInput: filterInputTypes.text, + }, + energy: { + fieldName: 'Mean energy [GeV]', + filterInput: filterInputTypes.number, + }, +}; + +module.exports = oldPeriodFieldNames; diff --git a/app/config/rct-data/viewFieldNames/periodFieldNames.js b/app/config/rct-data/viewFieldNames/periodFieldNames.js index 2618e1cd6..9d08daa89 100644 --- a/app/config/rct-data/viewFieldNames/periodFieldNames.js +++ b/app/config/rct-data/viewFieldNames/periodFieldNames.js @@ -18,18 +18,18 @@ const periodFieldNames = { fieldName: 'Name', filterInput: filterInputTypes.text, }, - year: { - fieldName: 'Year', - filterInput: filterInputTypes.number, - }, - beam: { + beamType: { fieldName: 'Beam', filterInput: filterInputTypes.text, }, - energy: { + avgEnergy: { fieldName: 'Mean energy [GeV]', filterInput: filterInputTypes.number, }, + distinctEnergies: { + fieldName: 'Energies [GeV]', + filterInput: filterInputTypes.number, + }, }; module.exports = periodFieldNames; diff --git a/app/public/views/periods/Periods.js b/app/public/views/periods/Periods.js index 85fcf3751..05ba8bac6 100644 --- a/app/public/views/periods/Periods.js +++ b/app/public/views/periods/Periods.js @@ -14,6 +14,7 @@ import { Observable, RemoteData } from '/js/src/index.js'; import { PaginationModel } from '../../components/table/pagination/PaginationModel.js'; import { getRemoteDataSlice } from '../../utils/fetch/getRemoteDataSlice.js'; +import { RCT } from '../../config.js'; /** * Model representing handlers for periods page @@ -33,12 +34,11 @@ export default class PeriodsModel extends Observable { this._pagination = new PaginationModel(model.userPreferences); this._pagination.observe(() => this.fetchAllPeriods()); - this._visibleFields = null; + this._visibleFields = Object.keys(RCT.fieldNames.periods); /* - * Content - * this._currentPagePeriods = RemoteData.notAsked(); - * this._allPeriods = RemoteData.notAsked(); + *This._currentPagePeriods = RemoteData.notAsked(); + *this._allPeriods = RemoteData.notAsked(); */ this._periods = RemoteData.NotAsked(); @@ -51,7 +51,7 @@ export default class PeriodsModel extends Observable { */ reset() { this._periods = RemoteData.NotAsked(); - this._visibleFields = null; + this._visibleFields = Object.keys(RCT.fieldNames.periods); this._pagination.reset(); } @@ -113,4 +113,13 @@ export default class PeriodsModel extends Observable { get periods() { return this._periods; } + + /** + * Retro-compatibility access to paginated runs, returning all the runs contained in the current page if it applies + * + * @return {RemoteData} the runs in the current page + */ + get visibleFields() { + return this._visibleFields; + } } diff --git a/app/public/views/periods/overview/periodsContent.js b/app/public/views/periods/overview/periodsContent.js index 0d749b951..6f44e315d 100644 --- a/app/public/views/periods/overview/periodsContent.js +++ b/app/public/views/periods/overview/periodsContent.js @@ -19,6 +19,7 @@ import dataActionButtons, { dataActions } from '../../../components/buttons/data import filter from '../../userView/data/table/filtering/filter.js'; import { anyFiltersActive } from '../../../utils/filtering/filterUtils.js'; import activeFilters from '../../userView/data/table/filtering/activeFilters.js'; +import { noDataFound, noMatchingData } from '../../../components/messagePanel/messages.js'; const pageName = RCT.pageNames.periods; /** @@ -52,8 +53,11 @@ export default function periodsContent(periods, model) { model.showFilteringPanel ? filter(dataAccess) : '', anyFiltersActive(url) ? activeFilters(dataAccess, url) : '', - // Periods.length > 0 - table(periods)); + periods.length > 0 + ? table(periods) + : anyFiltersActive(url) + ? noMatchingData(dataAccess, pageName) + : noDataFound(dataAccess)); /* *Return h('.p-1rem', [ From cce4554a1abe90a2d7071a7722c286a15c6fcfac Mon Sep 17 00:00:00 2001 From: Ehevi Date: Tue, 15 Aug 2023 15:01:04 +0200 Subject: [PATCH 10/82] Display periods from the new API --- .../table/pagination/PaginationModel.js | 12 +++++ app/public/views/periods/Periods.js | 19 ++++--- .../views/periods/overview/periodsContent.js | 49 ++++++------------- .../views/periods/overview/periodsPanel.js | 2 +- .../views/periods/table/periodsTableHeader.js | 47 ++++++++++++++++++ .../views/periods/table/periodsTableRow.js | 34 +++++++++++++ 6 files changed, 120 insertions(+), 43 deletions(-) create mode 100644 app/public/views/periods/table/periodsTableHeader.js create mode 100644 app/public/views/periods/table/periodsTableRow.js diff --git a/app/public/components/table/pagination/PaginationModel.js b/app/public/components/table/pagination/PaginationModel.js index 5c965d8dc..94bb6cd46 100644 --- a/app/public/components/table/pagination/PaginationModel.js +++ b/app/public/components/table/pagination/PaginationModel.js @@ -73,6 +73,18 @@ export class PaginationModel extends Observable { } } + /** + * Navigate to the target page + * + * @param {number} page target page + * @return {void} + */ + goToPage(page) { + if (page < this.pagesCount) { + this.currentPage = page; + } + } + /** * Returns the current page * diff --git a/app/public/views/periods/Periods.js b/app/public/views/periods/Periods.js index 05ba8bac6..6947a102d 100644 --- a/app/public/views/periods/Periods.js +++ b/app/public/views/periods/Periods.js @@ -34,7 +34,7 @@ export default class PeriodsModel extends Observable { this._pagination = new PaginationModel(model.userPreferences); this._pagination.observe(() => this.fetchAllPeriods()); - this._visibleFields = Object.keys(RCT.fieldNames.periods); + this._fields = Object.keys(RCT.fieldNames.periods).map((field) => ({ name: field, visible: true })); /* *This._currentPagePeriods = RemoteData.notAsked(); @@ -51,7 +51,7 @@ export default class PeriodsModel extends Observable { */ reset() { this._periods = RemoteData.NotAsked(); - this._visibleFields = Object.keys(RCT.fieldNames.periods); + this._fields = Object.keys(RCT.fieldNames.periods).map((field) => ({ fieldName: field, marked: true })); this._pagination.reset(); } @@ -114,12 +114,15 @@ export default class PeriodsModel extends Observable { return this._periods; } - /** - * Retro-compatibility access to paginated runs, returning all the runs contained in the current page if it applies - * - * @return {RemoteData} the runs in the current page - */ get visibleFields() { - return this._visibleFields; + return this._fields.filter((field) => field.visible); + } + + get fields() { + return this._fields; + } + + get pagination() { + return this._pagination; } } diff --git a/app/public/views/periods/overview/periodsContent.js b/app/public/views/periods/overview/periodsContent.js index 6f44e315d..da0263eed 100644 --- a/app/public/views/periods/overview/periodsContent.js +++ b/app/public/views/periods/overview/periodsContent.js @@ -12,7 +12,6 @@ */ import { h } from '/js/src/index.js'; -// Import pagesCellsSpecials from '../../userView/data/pagesCellsSpecials.js'; import { RCT } from '../../../config.js'; import title from '../../../components/table/title.js'; import dataActionButtons, { dataActions } from '../../../components/buttons/dataActionButtons.js'; @@ -20,6 +19,8 @@ import filter from '../../userView/data/table/filtering/filter.js'; import { anyFiltersActive } from '../../../utils/filtering/filterUtils.js'; import activeFilters from '../../userView/data/table/filtering/activeFilters.js'; import { noDataFound, noMatchingData } from '../../../components/messagePanel/messages.js'; +import periodsTableHeader from '../table/periodsTableHeader.js'; +import periodsTableRow from '../table/periodsTableRow.js'; const pageName = RCT.pageNames.periods; /** @@ -37,11 +38,9 @@ const applicableDataActions = { [dataActions.showFilteringPanel]: true, }; -export default function periodsContent(periods, model) { - const table = (periods) => periods.map((period) => h('', period.id)); +export default function periodsContent(periodsModel, periods, model) { const { dataAccess } = model; - // Const cellsSpecials = pagesCellsSpecials[pageName]; const url = model.router.getUrl(); return h('.p-1rem', @@ -54,37 +53,19 @@ export default function periodsContent(periods, model) { anyFiltersActive(url) ? activeFilters(dataAccess, url) : '', periods.length > 0 - ? table(periods) + ? periodsModel.visibleFields.length > 0 + ? h('.p-top-05em', + h('.x-scrollable-table.border-sh', + h(`table.${pageName}-table`, { + id: `data-table-${pageName}`, + }, + periodsTableHeader(pageName, periodsModel.visibleFields, periods, model), + h('tbody', { id: `table-body-${pageName}` }, + periods.map((period) => periodsTableRow( + period, periodsModel.visibleFields, + )))))) + : '' : anyFiltersActive(url) ? noMatchingData(dataAccess, pageName) : noDataFound(dataAccess)); - - /* - *Return h('.p-1rem', [ - * h('.flex-wrap.justify-between.items-center', - * h('.flex-wrap.justify-between.items-center', - * title(dataPointer.page)), - * - * dataActionButtons(model, applicableDataActions)), - * model.showFilteringPanel ? filter(model) : '', - * anyFiltersActive(url) ? activeFilters(model, url) : '', - * - * data.rows?.length > 0 - * ? visibleFields.length > 0 - * ? h('.p-top-05em', - * h('.x-scrollable-table.border-sh', - * pager(model, data, false), - * h(`table.${dataPointer.page}-table`, { - * id: `data-table-${data.url}`, - * }, - * tableHeader(visibleFields, data, model), - * model.sortingRowVisible ? sortingRow(visibleFields, data, model) : '', - * tableBody(model, visibleFields, data, cellsSpecials, runs)), - * data.rows.length > 15 ? pager(model, data) : '')) - * : '' - * : anyFiltersActive(url) - * ? noMatchingData(model, dataPointer.page) - * : noDataFound(model), - *]); - */ } diff --git a/app/public/views/periods/overview/periodsPanel.js b/app/public/views/periods/overview/periodsPanel.js index 42ddd78d0..95b4b9beb 100644 --- a/app/public/views/periods/overview/periodsPanel.js +++ b/app/public/views/periods/overview/periodsPanel.js @@ -22,7 +22,7 @@ export default function periodsPanel(model) { return periods.match({ NotAsked: () => unknown(dataAccess), Loading: () => waiting(), - Success: () => periodsContent(periods.payload, model), + Success: () => periodsContent(model.periods, periods.payload, model), Failure: (errors) => failureWithMessage(model, errors), }); } diff --git a/app/public/views/periods/table/periodsTableHeader.js b/app/public/views/periods/table/periodsTableHeader.js new file mode 100644 index 000000000..15d610d19 --- /dev/null +++ b/app/public/views/periods/table/periodsTableHeader.js @@ -0,0 +1,47 @@ +/** + * @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 { getHeaderSpecial, headerSpecPresent, nonDisplayable } from '../../userView/data/headersSpecials.js'; + +export default function periodsTableHeader(pageName, visibleFields, data, model) { + const columnsHeadersArray = (visibleFields, model) => { + const dataHeaders = visibleFields.map((field) => + h(`th.${pageName}-${field.name}-header`, { + scope: 'col', + }, h('.relative', [ + headerSpecPresent(model, field) !== nonDisplayable + ? h('.inline', getHeaderSpecial(model, field)) + : '', + ]))); + return dataHeaders; + }; + + const rowsOptions = (model, data) => + h('th', { scope: 'col' }, + h('.relative', + h(`input.checkbox.abs-center${data.every((r) => r.marked) ? '.ticked' : ''}`, { + type: 'checkbox', + onclick: (e) => { + for (const row of data) { + row.marked = e.target.checked; + } + model.notify(); + }, + checked: data.every((r) => r.marked), + }))); + + return h('thead.header', + h('tr', [rowsOptions(model.dataAccess, data)].concat(columnsHeadersArray(visibleFields, model.dataAccess)))); +} diff --git a/app/public/views/periods/table/periodsTableRow.js b/app/public/views/periods/table/periodsTableRow.js new file mode 100644 index 000000000..5d0c81993 --- /dev/null +++ b/app/public/views/periods/table/periodsTableRow.js @@ -0,0 +1,34 @@ +/** + * @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 { RCT } from '../../../config.js'; + +export default function periodsTableRow(periodData, visibleFields) { + const pageName = RCT.pageNames.periods; + + const dataCells = visibleFields.map((field) => + h(`td.${pageName}-${field.name}-cell.text-ellipsis`, + periodData[field.name])); + + const checkbox = h('td.relative.track', + h(`input.checkbox.abs-center${periodData.marked ? '.ticked' : ''}`, { + type: 'checkbox', + checked: periodData.marked, + })); + + return h('tr.track', + checkbox, + dataCells); +} From b3c38e5e5a9301b5c39a88c3f3959bd7af9f0509 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Tue, 15 Aug 2023 15:02:07 +0200 Subject: [PATCH 11/82] Run energies --- app/config/rct-data/viewFieldNames/periodFieldNames.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config/rct-data/viewFieldNames/periodFieldNames.js b/app/config/rct-data/viewFieldNames/periodFieldNames.js index 9d08daa89..467f4b687 100644 --- a/app/config/rct-data/viewFieldNames/periodFieldNames.js +++ b/app/config/rct-data/viewFieldNames/periodFieldNames.js @@ -27,7 +27,7 @@ const periodFieldNames = { filterInput: filterInputTypes.number, }, distinctEnergies: { - fieldName: 'Energies [GeV]', + fieldName: 'Run energies [GeV]', filterInput: filterInputTypes.number, }, }; From b2ca9016ab33ce39df0b32a47daf548262b927c5 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Tue, 15 Aug 2023 15:13:28 +0200 Subject: [PATCH 12/82] Periods css update --- app/public/styles/rct/custom/tables/periods.css | 15 +++++++++++---- .../views/userView/data/pagesCellsSpecials.js | 12 ++++++------ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/app/public/styles/rct/custom/tables/periods.css b/app/public/styles/rct/custom/tables/periods.css index e9dc789b2..e1ad1b0f4 100644 --- a/app/public/styles/rct/custom/tables/periods.css +++ b/app/public/styles/rct/custom/tables/periods.css @@ -60,15 +60,22 @@ } /* Beam types */ -.rct .periods-table .periods-beam-cell, -.rct .periods-table .periods-beam-header { +.rct .periods-table .periods-beamType-cell, +.rct .periods-table .periods-beamType-header { min-width: 80px; text-align: right; } /* Mean energy */ -.rct .periods-table .periods-energy-cell, -.rct .periods-table .periods-energy-header { +.rct .periods-table .periods-avgEnergy-cell, +.rct .periods-table .periods-avgEnergy-header { + min-width: 60px; + text-align: right; +} + +/* Run energied */ +.rct .periods-table .periods-distinctEnergies-cell, +.rct .periods-table .periods-distinctEnergies-header { min-width: 60px; text-align: right; } diff --git a/app/public/views/userView/data/pagesCellsSpecials.js b/app/public/views/userView/data/pagesCellsSpecials.js index b5348e97d..ebf4dd03c 100644 --- a/app/public/views/userView/data/pagesCellsSpecials.js +++ b/app/public/views/userView/data/pagesCellsSpecials.js @@ -51,19 +51,19 @@ pagesCellsSpecials[PN.periods] = { model.navigation, 'runs', // eslint-disable-next-line max-len - `/?page=${PN.runsPerPeriod}&index=${item.name}&${DRP.rowsOnSite}=${model.userPreferences.rowsOnSite}&${DRP.site}=1&sorting=-run_number`, + `/?page=${PN.runsPerPeriod}&index=${item.name}&${DRP.rowsOnSite}=${model.parent.userPreferences.rowsOnSite}&${DRP.site}=1&sorting=-run_number`, ), linkChip( model.navigation, 'data passes', - `/?page=${PN.dataPasses}&index=${item.name}&${DRP.rowsOnSite}=${model.userPreferences.rowsOnSite}&${DRP.site}=1`, + `/?page=${PN.dataPasses}&index=${item.name}&${DRP.rowsOnSite}=${model.parent.userPreferences.rowsOnSite}&${DRP.site}=1`, ), linkChip( model.navigation, 'MC', - `/?page=${PN.mc}&index=${item.name}&${DRP.rowsOnSite}=${model.userPreferences.rowsOnSite}&${DRP.site}=1`, + `/?page=${PN.mc}&index=${item.name}&${DRP.rowsOnSite}=${model.parent.userPreferences.rowsOnSite}&${DRP.site}=1`, )), ], @@ -80,14 +80,14 @@ pagesCellsSpecials[PN.dataPasses] = { onclick: async () => { await runs.fetchRunsPerDataPass(item.name); // eslint-disable-next-line max-len - model.router.go(`/?page=${PN.runsPerDataPass}&index=${item.name}&${DRP.rowsOnSite}=${model.userPreferences.rowsOnSite}&${DRP.site}=1&sorting=-run_number`); + model.router.go(`/?page=${PN.runsPerDataPass}&index=${item.name}&${DRP.rowsOnSite}=${model.parent.userPreferences.rowsOnSite}&${DRP.site}=1&sorting=-run_number`); }, }, 'runs'), linkChip( model.navigation, 'anchorage', // eslint-disable-next-line max-len - `/?page=${PN.anchoragePerDatapass}&index=${item.name}&${DRP.rowsOnSite}=${model.userPreferences.rowsOnSite}&${DRP.site}=1&sorting=-name`, + `/?page=${PN.anchoragePerDatapass}&index=${item.name}&${DRP.rowsOnSite}=${model.parent.userPreferences.rowsOnSite}&${DRP.site}=1&sorting=-name`, )), ], size: (model, runs, item) => getReadableFileSizeString(Number(item.size)), @@ -100,7 +100,7 @@ pagesCellsSpecials[PN.mc] = { model.navigation, 'anchored', // eslint-disable-next-line max-len - `/?page=${PN.anchoredPerMC}&index=${item.name}&${DRP.rowsOnSite}=${model.userPreferences.rowsOnSite}&${DRP.site}=1&sorting=-name`, + `/?page=${PN.anchoredPerMC}&index=${item.name}&${DRP.rowsOnSite}=${model.parent.userPreferences.rowsOnSite}&${DRP.site}=1&sorting=-name`, )), ], }; From 31cd33b8041aa7b545964692be5e07758e35a028 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Tue, 15 Aug 2023 15:26:16 +0200 Subject: [PATCH 13/82] Update user preferences access --- app/public/model/data/FetchedDataManager.js | 4 ++-- app/public/views/periods/overview/periodsContent.js | 2 +- app/public/views/periods/table/periodsTableRow.js | 10 ++++++++-- app/public/views/runs/runsPerDataPass/table/header.js | 2 +- app/public/views/runs/runsPerDataPass/table/row.js | 2 +- app/public/views/runs/runsPerPeriod/table/header.js | 2 +- app/public/views/runs/runsPerPeriod/table/row.js | 2 +- 7 files changed, 15 insertions(+), 9 deletions(-) diff --git a/app/public/model/data/FetchedDataManager.js b/app/public/model/data/FetchedDataManager.js index 366e4ba80..fc5c01e22 100644 --- a/app/public/model/data/FetchedDataManager.js +++ b/app/public/model/data/FetchedDataManager.js @@ -51,7 +51,7 @@ export default class FetchedDataManager { if (url === null) { url = this.router.getUrl(); if (!url.searchParams.has(dataReqParams.rowsOnSite)) { - url = new URL(`${url.href}&${dataReqParams.rowsOnSite}=${this.model.userPreferences.rowsOnSite}`); + url = new URL(`${url.href}&${dataReqParams.rowsOnSite}=${this.model.parent.userPreferences.rowsOnSite}`); } if (!url.searchParams.has(dataReqParams.site)) { url = new URL(`${url.href}&${dataReqParams.site}=1`); @@ -140,7 +140,7 @@ export default class FetchedDataManager { changeRowsOnSite() { const url = this.router.getUrl(); - const newUrl = replaceUrlParams(url, { [dataReqParams.rowsOnSite]: this.model.userPreferences.rowsOnSite }); + const newUrl = replaceUrlParams(url, { [dataReqParams.rowsOnSite]: this.model.parent.userPreferences.rowsOnSite }); this.router.go(newUrl); } diff --git a/app/public/views/periods/overview/periodsContent.js b/app/public/views/periods/overview/periodsContent.js index da0263eed..c943bb10b 100644 --- a/app/public/views/periods/overview/periodsContent.js +++ b/app/public/views/periods/overview/periodsContent.js @@ -62,7 +62,7 @@ export default function periodsContent(periodsModel, periods, model) { periodsTableHeader(pageName, periodsModel.visibleFields, periods, model), h('tbody', { id: `table-body-${pageName}` }, periods.map((period) => periodsTableRow( - period, periodsModel.visibleFields, + period, periodsModel.visibleFields, dataAccess, )))))) : '' : anyFiltersActive(url) diff --git a/app/public/views/periods/table/periodsTableRow.js b/app/public/views/periods/table/periodsTableRow.js index 5d0c81993..1b3f59d0b 100644 --- a/app/public/views/periods/table/periodsTableRow.js +++ b/app/public/views/periods/table/periodsTableRow.js @@ -14,13 +14,19 @@ import { h } from '/js/src/index.js'; import { RCT } from '../../../config.js'; +import pagesCellsSpecials from '../../userView/data/pagesCellsSpecials.js'; -export default function periodsTableRow(periodData, visibleFields) { +export default function periodsTableRow(periodData, visibleFields, model) { const pageName = RCT.pageNames.periods; + const cellsSpecials = pagesCellsSpecials[pageName]; const dataCells = visibleFields.map((field) => h(`td.${pageName}-${field.name}-cell.text-ellipsis`, - periodData[field.name])); + periodData[field.name] + ? cellsSpecials[field.name] + ? cellsSpecials[field.name](model, periodData) + : periodData[field.name] + : '')); const checkbox = h('td.relative.track', h(`input.checkbox.abs-center${periodData.marked ? '.ticked' : ''}`, { diff --git a/app/public/views/runs/runsPerDataPass/table/header.js b/app/public/views/runs/runsPerDataPass/table/header.js index a2aa2b575..2e341b864 100644 --- a/app/public/views/runs/runsPerDataPass/table/header.js +++ b/app/public/views/runs/runsPerDataPass/table/header.js @@ -30,7 +30,7 @@ export default function header(visibleFields, data, model) { ]))); const detectorHeaders = visibleFields.filter((field) => - shouldDisplayDetectorField(field.name, model.userPreferences.detectorList)).map((field) => + shouldDisplayDetectorField(field.name, model.parent.userPreferences.detectorList)).map((field) => h(`th.${pageName}-detector-header`, { scope: 'col', }, h('.relative', [ diff --git a/app/public/views/runs/runsPerDataPass/table/row.js b/app/public/views/runs/runsPerDataPass/table/row.js index f486d9e36..fc8720255 100644 --- a/app/public/views/runs/runsPerDataPass/table/row.js +++ b/app/public/views/runs/runsPerDataPass/table/row.js @@ -31,7 +31,7 @@ export default function row( : '')); const detectorCells = visibleFields.filter((field) => - shouldDisplayDetectorField(field.name, model.userPreferences.detectorList)).map((field) => + shouldDisplayDetectorField(field.name, model.parent.userPreferences.detectorList)).map((field) => h(`td.${pageName}-detector-cell.text-ellipsis`, item[field.name] ? detectorIcon(model.navigation, item, index, detectors.getDetectorName(field.name), true, true) diff --git a/app/public/views/runs/runsPerPeriod/table/header.js b/app/public/views/runs/runsPerPeriod/table/header.js index 2e236bee1..604d0c4c7 100644 --- a/app/public/views/runs/runsPerPeriod/table/header.js +++ b/app/public/views/runs/runsPerPeriod/table/header.js @@ -30,7 +30,7 @@ export default function header(visibleFields, data, model) { ]))); const detectorHeaders = visibleFields.filter((field) => - shouldDisplayDetectorField(field.name, model.userPreferences.detectorList)).map((field) => + shouldDisplayDetectorField(field.name, model.parent.userPreferences.detectorList)).map((field) => h(`th.${pageName}-detector-header`, { scope: 'col', }, h('.relative', [ diff --git a/app/public/views/runs/runsPerPeriod/table/row.js b/app/public/views/runs/runsPerPeriod/table/row.js index ed5dc201e..8e0d9a3b6 100644 --- a/app/public/views/runs/runsPerPeriod/table/row.js +++ b/app/public/views/runs/runsPerPeriod/table/row.js @@ -31,7 +31,7 @@ export default function row( : '')); const detectorCells = visibleFields.filter((field) => - shouldDisplayDetectorField(field.name, model.userPreferences.detectorList)).map((field) => + shouldDisplayDetectorField(field.name, model.parent.userPreferences.detectorList)).map((field) => h(`td.${pageName}-detector-cell.text-ellipsis`, item[field.name] ? detectorIcon(model.navigation, item, index, detectors.getDetectorName(field.name)) From ebcb8a45c428114bd1ee892a992c5bacbb846df4 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Tue, 15 Aug 2023 15:44:07 +0200 Subject: [PATCH 14/82] Enable data selection --- app/public/views/periods/Periods.js | 11 +++++++++++ app/public/views/periods/overview/periodsContent.js | 2 +- app/public/views/periods/table/periodsTableHeader.js | 6 +++--- app/public/views/periods/table/periodsTableRow.js | 12 ++++++++---- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/app/public/views/periods/Periods.js b/app/public/views/periods/Periods.js index 6947a102d..4ca353118 100644 --- a/app/public/views/periods/Periods.js +++ b/app/public/views/periods/Periods.js @@ -36,6 +36,8 @@ export default class PeriodsModel extends Observable { this._fields = Object.keys(RCT.fieldNames.periods).map((field) => ({ name: field, visible: true })); + this._hideSelectedPeriods = false; + /* *This._currentPagePeriods = RemoteData.notAsked(); *this._allPeriods = RemoteData.notAsked(); @@ -125,4 +127,13 @@ export default class PeriodsModel extends Observable { get pagination() { return this._pagination; } + + get hideSelectedPeriods() { + return this._hideSelectedPeriods; + } + + toggleSelection(period) { + period.selected = !period.selected; + this.notify(); + } } diff --git a/app/public/views/periods/overview/periodsContent.js b/app/public/views/periods/overview/periodsContent.js index c943bb10b..311304386 100644 --- a/app/public/views/periods/overview/periodsContent.js +++ b/app/public/views/periods/overview/periodsContent.js @@ -62,7 +62,7 @@ export default function periodsContent(periodsModel, periods, model) { periodsTableHeader(pageName, periodsModel.visibleFields, periods, model), h('tbody', { id: `table-body-${pageName}` }, periods.map((period) => periodsTableRow( - period, periodsModel.visibleFields, dataAccess, + period, periodsModel.visibleFields, dataAccess, periodsModel )))))) : '' : anyFiltersActive(url) diff --git a/app/public/views/periods/table/periodsTableHeader.js b/app/public/views/periods/table/periodsTableHeader.js index 15d610d19..c837be2c1 100644 --- a/app/public/views/periods/table/periodsTableHeader.js +++ b/app/public/views/periods/table/periodsTableHeader.js @@ -31,15 +31,15 @@ export default function periodsTableHeader(pageName, visibleFields, data, model) const rowsOptions = (model, data) => h('th', { scope: 'col' }, h('.relative', - h(`input.checkbox.abs-center${data.every((r) => r.marked) ? '.ticked' : ''}`, { + h(`input.checkbox.abs-center${data.every((r) => r.selected) ? '.ticked' : ''}`, { type: 'checkbox', onclick: (e) => { for (const row of data) { - row.marked = e.target.checked; + row.selected = e.target.checked; } model.notify(); }, - checked: data.every((r) => r.marked), + checked: data.every((r) => r.selected), }))); return h('thead.header', diff --git a/app/public/views/periods/table/periodsTableRow.js b/app/public/views/periods/table/periodsTableRow.js index 1b3f59d0b..8012f8dcb 100644 --- a/app/public/views/periods/table/periodsTableRow.js +++ b/app/public/views/periods/table/periodsTableRow.js @@ -15,8 +15,9 @@ import { h } from '/js/src/index.js'; import { RCT } from '../../../config.js'; import pagesCellsSpecials from '../../userView/data/pagesCellsSpecials.js'; +import { rowDisplayStyle } from '../../../utils/dataProcessing/dataProcessingUtils.js'; -export default function periodsTableRow(periodData, visibleFields, model) { +export default function periodsTableRow(periodData, visibleFields, model, periodsModel) { const pageName = RCT.pageNames.periods; const cellsSpecials = pagesCellsSpecials[pageName]; @@ -29,12 +30,15 @@ export default function periodsTableRow(periodData, visibleFields, model) { : '')); const checkbox = h('td.relative.track', - h(`input.checkbox.abs-center${periodData.marked ? '.ticked' : ''}`, { + h(`input.checkbox.abs-center${periodData.selected ? '.ticked' : ''}`, { type: 'checkbox', - checked: periodData.marked, + checked: periodData.selected, + onclick: () => { + periodsModel.toggleSelection(periodData); + }, })); - return h('tr.track', + return h(`tr.track${rowDisplayStyle(periodData.selected, periodsModel.hideMarkedRecords)}`, checkbox, dataCells); } From 544140f7115c7b94077916fb3e60bad463b7c3c5 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Tue, 15 Aug 2023 16:54:04 +0200 Subject: [PATCH 15/82] Add column filtering options --- .../components/table/newItemsCounter.js | 24 +++++++ app/public/components/table/newPager.js | 68 +++++++++++++++++++ app/public/components/table/pager.js | 2 +- app/public/views/periods/Periods.js | 46 +++++++++---- .../views/periods/overview/periodsContent.js | 4 +- .../views/periods/overview/periodsPanel.js | 6 +- .../runs/runsPerDataPass/overview/content.js | 2 +- 7 files changed, 131 insertions(+), 21 deletions(-) create mode 100644 app/public/components/table/newItemsCounter.js create mode 100644 app/public/components/table/newPager.js diff --git a/app/public/components/table/newItemsCounter.js b/app/public/components/table/newItemsCounter.js new file mode 100644 index 000000000..d11cf0fad --- /dev/null +++ b/app/public/components/table/newItemsCounter.js @@ -0,0 +1,24 @@ +/** + * @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. + */ + +export default function newItemsCounter(paginationModel) { + const currentSite = paginationModel.currentPage; + + const firstRowIdx = (currentSite - 1) * paginationModel.itemsPerPage + 1; + const lastRowIdx = currentSite * paginationModel.itemsPerPage > paginationModel.itemsCount + ? paginationModel.itemsCount + : currentSite * paginationModel.itemsPerPage; + + return `${firstRowIdx}-${lastRowIdx} of ${paginationModel.itemsCount}`; +} diff --git a/app/public/components/table/newPager.js b/app/public/components/table/newPager.js new file mode 100644 index 000000000..b720b7231 --- /dev/null +++ b/app/public/components/table/newPager.js @@ -0,0 +1,68 @@ +/** + * @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, iconChevronBottom } from '/js/src/index.js'; +import newItemsCounter from './newItemsCounter.js'; + +const columnDisplayOptions = { + nonEmpty: 'nonEmty', + all: 'all', +}; + +export default function newPager(periodsModel, model) { + const columnOptionsSelectId = 'columns-option-select-id'; + + function handleOptionChange() { + const columnsOptionsSelect = document.getElementById(columnOptionsSelectId); + const selectedValue = columnsOptionsSelect.options[columnsOptionsSelect.selectedIndex].value; + switch (selectedValue) { + case columnDisplayOptions.nonEmpty: + periodsModel.fields.forEach((field) => { + periodsModel.toggleFieldVisibility(field, periodsModel.currentPagePeriods.payload.some((p) => p[field.name])); + }); + break; + case columnDisplayOptions.all: + for (const field of periodsModel.fields) { + periodsModel.toggleFieldVisibility(field, true); + } + model.notify(); + break; + default: + break; + } + } + + return h('.flex-row.pager-panel.items-center', [ + h('.flex-wrap.justify-between.items-center', + h('.flex-wrap.justify-between.items-center.ph3', + h('.italic', newItemsCounter(periodsModel.pagination))), + + h('button.btn.icon-only-button.m-right-15', { + className: periodsModel.sortingRowVisible ? 'btn-primary' : 'btn-secondary', + onclick: () => periodsModel.toggleSortingRowVisibility(), + }, periodsModel.sortingRowVisible ? h('.sorting-20-off-white.abs-center') : h('.sorting-20-primary.abs-center')), + + h('select.select.column-options-select', { + id: columnOptionsSelectId, + name: columnOptionsSelectId, + onchange: () => handleOptionChange(), + }, + [ + h('option', { value: columnDisplayOptions.nonEmpty }, 'Non empty columns'), + h('option', { value: columnDisplayOptions.all }, 'All columns'), + // ToDo add customizable option => open modal here + ], iconChevronBottom())), + + ]); +} diff --git a/app/public/components/table/pager.js b/app/public/components/table/pager.js index 94c95be40..f51484a64 100644 --- a/app/public/components/table/pager.js +++ b/app/public/components/table/pager.js @@ -13,7 +13,7 @@ */ import { h, iconChevronBottom } from '/js/src/index.js'; -import itemsCounter from '../../views/userView/data/table/items-counter.js'; +import itemsCounter from '../../views/userView/data/table/items-counter.js'; // import { RCT } from '../../config.js'; const { site } = RCT.dataReqParams; diff --git a/app/public/views/periods/Periods.js b/app/public/views/periods/Periods.js index 4ca353118..4203186d5 100644 --- a/app/public/views/periods/Periods.js +++ b/app/public/views/periods/Periods.js @@ -37,11 +37,10 @@ export default class PeriodsModel extends Observable { this._fields = Object.keys(RCT.fieldNames.periods).map((field) => ({ name: field, visible: true })); this._hideSelectedPeriods = false; + this._sortingRowVisible = false; - /* - *This._currentPagePeriods = RemoteData.notAsked(); - *this._allPeriods = RemoteData.notAsked(); - */ + this._currentPagePeriods = RemoteData.notAsked(); + this._allPeriods = RemoteData.notAsked(); this._periods = RemoteData.NotAsked(); } @@ -90,30 +89,29 @@ export default class PeriodsModel extends Observable { try { const { items, totalCount } = await getRemoteDataSlice(endpoint); const concatenateWith = shouldKeepExisting ? this._periods.payload || [] : []; - this._periods = RemoteData.success([...concatenateWith, ...items]); + this._currentPagePeriods = RemoteData.success([...concatenateWith, ...items]); this._pagination.itemsCount = totalCount; } catch (errors) { - this._periods = RemoteData.failure(errors); + this._currentPagePeriods = RemoteData.failure(errors); } this.notify(); } /** - * Getter for all the run data - * @returns {RemoteData} Returns all of the filtered periods + * Get all the periods + * @return {RemoteData} periods */ - getPeriods() { - return this._allPeriods; + get periods() { + return this._periods; } /** - * Retro-compatibility access to paginated runs, returning all the runs contained in the current page if it applies - * - * @return {RemoteData} the runs in the current page + * Get current page periods + * @return {RemoteData} periods in the current page */ - get periods() { - return this._periods; + get currentPagePeriods() { + return this._currentPagePeriods; } get visibleFields() { @@ -132,8 +130,26 @@ export default class PeriodsModel extends Observable { return this._hideSelectedPeriods; } + get sortingRowVisible() { + return this._sortingRowVisible; + } + toggleSelection(period) { period.selected = !period.selected; this.notify(); } + + toggleSortingRowVisibility() { + this._sortingRowVisible = !this._sortingRowVisible; + this.notify(); + } + + toggleFieldVisibility(targetField) { + const targetFieldIndex = this._fields.findIndex((f) => f.name === targetField.name); + const targetState = arguments[1] !== undefined + ? arguments[1] + : !this._fields[targetFieldIndex].visible; + this._fields[targetFieldIndex].visible = targetState; + this.notify(); + } } diff --git a/app/public/views/periods/overview/periodsContent.js b/app/public/views/periods/overview/periodsContent.js index 311304386..74a349fa4 100644 --- a/app/public/views/periods/overview/periodsContent.js +++ b/app/public/views/periods/overview/periodsContent.js @@ -21,6 +21,7 @@ import activeFilters from '../../userView/data/table/filtering/activeFilters.js' import { noDataFound, noMatchingData } from '../../../components/messagePanel/messages.js'; import periodsTableHeader from '../table/periodsTableHeader.js'; import periodsTableRow from '../table/periodsTableRow.js'; +import newPager from '../../../components/table/newPager.js'; const pageName = RCT.pageNames.periods; /** @@ -56,13 +57,14 @@ export default function periodsContent(periodsModel, periods, model) { ? periodsModel.visibleFields.length > 0 ? h('.p-top-05em', h('.x-scrollable-table.border-sh', + newPager(periodsModel, model), h(`table.${pageName}-table`, { id: `data-table-${pageName}`, }, periodsTableHeader(pageName, periodsModel.visibleFields, periods, model), h('tbody', { id: `table-body-${pageName}` }, periods.map((period) => periodsTableRow( - period, periodsModel.visibleFields, dataAccess, periodsModel + period, periodsModel.visibleFields, dataAccess, periodsModel, )))))) : '' : anyFiltersActive(url) diff --git a/app/public/views/periods/overview/periodsPanel.js b/app/public/views/periods/overview/periodsPanel.js index 95b4b9beb..e6bbc725d 100644 --- a/app/public/views/periods/overview/periodsPanel.js +++ b/app/public/views/periods/overview/periodsPanel.js @@ -16,13 +16,13 @@ import periodsContent from './periodsContent.js'; import { waiting, unknown, failureWithMessage } from '../../../components/messagePanel/messages.js'; export default function periodsPanel(model) { - const { periods } = model.periods; + const { currentPagePeriods } = model.periods; const { dataAccess } = model; - return periods.match({ + return currentPagePeriods.match({ NotAsked: () => unknown(dataAccess), Loading: () => waiting(), - Success: () => periodsContent(model.periods, periods.payload, model), + Success: () => periodsContent(model.periods, currentPagePeriods.payload, model), Failure: (errors) => failureWithMessage(model, errors), }); } diff --git a/app/public/views/runs/runsPerDataPass/overview/content.js b/app/public/views/runs/runsPerDataPass/overview/content.js index 77a7ee386..579de5d48 100644 --- a/app/public/views/runs/runsPerDataPass/overview/content.js +++ b/app/public/views/runs/runsPerDataPass/overview/content.js @@ -71,7 +71,7 @@ export default function content(model, runs, detectors) { ? visibleFields.length > 0 ? h('.p-top-05em', h('.x-scrollable-table.border-sh', - pager(model, data, false), + pager(model, data, false), // h('table.runs-table', { id: `data-table-${data.url}`, }, From a571ef624851aad9ad6e4ac6a6f2681f420172e0 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Tue, 15 Aug 2023 17:03:56 +0200 Subject: [PATCH 16/82] Add pagination --- app/public/components/table/newPager.js | 55 +++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/app/public/components/table/newPager.js b/app/public/components/table/newPager.js index b720b7231..a20df951e 100644 --- a/app/public/components/table/newPager.js +++ b/app/public/components/table/newPager.js @@ -22,6 +22,19 @@ const columnDisplayOptions = { export default function newPager(periodsModel, model) { const columnOptionsSelectId = 'columns-option-select-id'; + const currentSite = periodsModel.pagination.currentPage; + const sitesNumber = periodsModel.pagination.pagesCount; + + const pageButton = (targetSite) => h(`button.btn${targetSite === currentSite ? '.btn-primary' : '.btn-secondary'}.no-text-decoration`, { + onclick: () => periodsModel.pagination.goToPage(targetSite), + }, targetSite); + + const siteChangingController = (targetSite, content) => h('button.btn.btn-secondary.site-changing-controller', { + onclick: () => periodsModel.pagination.goToPage(targetSite), + }, content); + + const moreSitesLeft = currentSite > 2; + const moreSitesRight = currentSite < sitesNumber - 1; function handleOptionChange() { const columnsOptionsSelect = document.getElementById(columnOptionsSelectId); @@ -64,5 +77,47 @@ export default function newPager(periodsModel, model) { // ToDo add customizable option => open modal here ], iconChevronBottom())), + h('.flex.pager-buttons', + // Move to the first site + currentSite > 1 ? siteChangingController(1, h('.double-left-15-primary')) : ' ', + // Move one site back + currentSite > 1 ? siteChangingController(currentSite - 1, h('.back-15-primary')) : ' ', + + // Move to the middle of sites range [first, current] + moreSitesLeft + ? siteChangingController( + Math.floor(currentSite / 2), + h('.more-15-primary'), + ) + : '', + + currentSite > 1 ? pageButton(currentSite - 1) : '', + pageButton(currentSite), + currentSite < sitesNumber ? pageButton(currentSite + 1) : '', + + // Move to the middle of sites range [current, last] + moreSitesRight + ? siteChangingController( + currentSite + Math.floor((sitesNumber - currentSite) / 2), + h('.more-15-primary'), + ) + : '', + + // Move one site forward + currentSite < sitesNumber + ? siteChangingController( + currentSite + 1, + h('.forward-15-primary'), + ) + : '', + + // Move to the last site + currentSite < sitesNumber + ? siteChangingController( + sitesNumber, + h('.double-right-15-primary'), + ) + : ''), + ]); } From 81cd499b1c63e74d1cb352582aef23f3af015984 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 16 Aug 2023 11:57:10 +0200 Subject: [PATCH 17/82] Add year field --- app/config/rct-data/viewFieldNames/periodFieldNames.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/config/rct-data/viewFieldNames/periodFieldNames.js b/app/config/rct-data/viewFieldNames/periodFieldNames.js index 467f4b687..11e421438 100644 --- a/app/config/rct-data/viewFieldNames/periodFieldNames.js +++ b/app/config/rct-data/viewFieldNames/periodFieldNames.js @@ -22,6 +22,10 @@ const periodFieldNames = { fieldName: 'Beam', filterInput: filterInputTypes.text, }, + year: { + fieldName: 'Year', + filterInput: filterInputTypes.number, + }, avgEnergy: { fieldName: 'Mean energy [GeV]', filterInput: filterInputTypes.number, From 01b45288fe638d71e028f942aba04ac4537f4461 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 16 Aug 2023 12:22:04 +0200 Subject: [PATCH 18/82] Improve display of the distinct energies --- app/public/styles/rct/custom/tables/periods.css | 2 +- app/public/views/userView/data/pagesCellsSpecials.js | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/public/styles/rct/custom/tables/periods.css b/app/public/styles/rct/custom/tables/periods.css index e1ad1b0f4..c2f8f91b4 100644 --- a/app/public/styles/rct/custom/tables/periods.css +++ b/app/public/styles/rct/custom/tables/periods.css @@ -73,7 +73,7 @@ text-align: right; } -/* Run energied */ +/* Run energies */ .rct .periods-table .periods-distinctEnergies-cell, .rct .periods-table .periods-distinctEnergies-header { min-width: 60px; diff --git a/app/public/views/userView/data/pagesCellsSpecials.js b/app/public/views/userView/data/pagesCellsSpecials.js index ebf4dd03c..51b467849 100644 --- a/app/public/views/userView/data/pagesCellsSpecials.js +++ b/app/public/views/userView/data/pagesCellsSpecials.js @@ -15,7 +15,10 @@ import { h } from '/js/src/index.js'; import { RCT } from '../../../config.js'; import { getReadableFileSizeString } from '../../../utils/utils.js'; import linkChip from '../../../components/chips/linkChip.js'; +import { getClosestDefinedEnergy } from '../../../utils/dataProcessing/dataProcessingUtils.js'; const { dataReqParams: DRP, pageNames: PN, outerServices } = RCT; +const acceptableEnergyValues = RCT.mapping.energy.values; +const acceptableEnergyMargin = RCT.mapping.energy.acceptableMargin; /** * Configuration what buttons at which cells and which pages are supposed @@ -67,9 +70,12 @@ pagesCellsSpecials[PN.periods] = { )), ], - energy: (model, item) => - `${Number(item.energy).toFixed(2)}` - , + avgEnergy: (model, item) => + `${Number(item.avgEnergy).toFixed(2)}`, + distinctEnergies: (model, item) => + h('', item.distinctEnergies.map((e) => getClosestDefinedEnergy(e, acceptableEnergyValues, acceptableEnergyMargin)) + .filter((value, index, array) => array.indexOf(value) === index) + .reduce((toDisplay, currentValue) => `${toDisplay ? `${toDisplay}, ` : ''}${currentValue}`, '')), }; pagesCellsSpecials[PN.dataPasses] = { From 091377a33cee9cc7d5667456054144d1e50ff272 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 16 Aug 2023 13:26:44 +0200 Subject: [PATCH 19/82] Rename pager to obsolete pager --- app/public/components/table/newPager.js | 123 ------------- app/public/components/table/obsoletePager.js | 135 ++++++++++++++ app/public/components/table/pager.js | 172 ++++++++---------- .../views/periods/overview/periodsContent.js | 4 +- .../runs/runsPerDataPass/overview/content.js | 6 +- .../runs/runsPerPeriod/overview/content.js | 6 +- .../views/userView/data/table/tablePanel.js | 6 +- 7 files changed, 226 insertions(+), 226 deletions(-) delete mode 100644 app/public/components/table/newPager.js create mode 100644 app/public/components/table/obsoletePager.js diff --git a/app/public/components/table/newPager.js b/app/public/components/table/newPager.js deleted file mode 100644 index a20df951e..000000000 --- a/app/public/components/table/newPager.js +++ /dev/null @@ -1,123 +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 { h, iconChevronBottom } from '/js/src/index.js'; -import newItemsCounter from './newItemsCounter.js'; - -const columnDisplayOptions = { - nonEmpty: 'nonEmty', - all: 'all', -}; - -export default function newPager(periodsModel, model) { - const columnOptionsSelectId = 'columns-option-select-id'; - const currentSite = periodsModel.pagination.currentPage; - const sitesNumber = periodsModel.pagination.pagesCount; - - const pageButton = (targetSite) => h(`button.btn${targetSite === currentSite ? '.btn-primary' : '.btn-secondary'}.no-text-decoration`, { - onclick: () => periodsModel.pagination.goToPage(targetSite), - }, targetSite); - - const siteChangingController = (targetSite, content) => h('button.btn.btn-secondary.site-changing-controller', { - onclick: () => periodsModel.pagination.goToPage(targetSite), - }, content); - - const moreSitesLeft = currentSite > 2; - const moreSitesRight = currentSite < sitesNumber - 1; - - function handleOptionChange() { - const columnsOptionsSelect = document.getElementById(columnOptionsSelectId); - const selectedValue = columnsOptionsSelect.options[columnsOptionsSelect.selectedIndex].value; - switch (selectedValue) { - case columnDisplayOptions.nonEmpty: - periodsModel.fields.forEach((field) => { - periodsModel.toggleFieldVisibility(field, periodsModel.currentPagePeriods.payload.some((p) => p[field.name])); - }); - break; - case columnDisplayOptions.all: - for (const field of periodsModel.fields) { - periodsModel.toggleFieldVisibility(field, true); - } - model.notify(); - break; - default: - break; - } - } - - return h('.flex-row.pager-panel.items-center', [ - h('.flex-wrap.justify-between.items-center', - h('.flex-wrap.justify-between.items-center.ph3', - h('.italic', newItemsCounter(periodsModel.pagination))), - - h('button.btn.icon-only-button.m-right-15', { - className: periodsModel.sortingRowVisible ? 'btn-primary' : 'btn-secondary', - onclick: () => periodsModel.toggleSortingRowVisibility(), - }, periodsModel.sortingRowVisible ? h('.sorting-20-off-white.abs-center') : h('.sorting-20-primary.abs-center')), - - h('select.select.column-options-select', { - id: columnOptionsSelectId, - name: columnOptionsSelectId, - onchange: () => handleOptionChange(), - }, - [ - h('option', { value: columnDisplayOptions.nonEmpty }, 'Non empty columns'), - h('option', { value: columnDisplayOptions.all }, 'All columns'), - // ToDo add customizable option => open modal here - ], iconChevronBottom())), - - h('.flex.pager-buttons', - // Move to the first site - currentSite > 1 ? siteChangingController(1, h('.double-left-15-primary')) : ' ', - // Move one site back - currentSite > 1 ? siteChangingController(currentSite - 1, h('.back-15-primary')) : ' ', - - // Move to the middle of sites range [first, current] - moreSitesLeft - ? siteChangingController( - Math.floor(currentSite / 2), - h('.more-15-primary'), - ) - : '', - - currentSite > 1 ? pageButton(currentSite - 1) : '', - pageButton(currentSite), - currentSite < sitesNumber ? pageButton(currentSite + 1) : '', - - // Move to the middle of sites range [current, last] - moreSitesRight - ? siteChangingController( - currentSite + Math.floor((sitesNumber - currentSite) / 2), - h('.more-15-primary'), - ) - : '', - - // Move one site forward - currentSite < sitesNumber - ? siteChangingController( - currentSite + 1, - h('.forward-15-primary'), - ) - : '', - - // Move to the last site - currentSite < sitesNumber - ? siteChangingController( - sitesNumber, - h('.double-right-15-primary'), - ) - : ''), - - ]); -} diff --git a/app/public/components/table/obsoletePager.js b/app/public/components/table/obsoletePager.js new file mode 100644 index 000000000..dc2174239 --- /dev/null +++ b/app/public/components/table/obsoletePager.js @@ -0,0 +1,135 @@ +/** + * @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, iconChevronBottom } from '/js/src/index.js'; +import itemsCounter from '../../views/userView/data/table/items-counter.js'; // +import { RCT } from '../../config.js'; + +const { site } = RCT.dataReqParams; + +export default function obsoletePager(model, data, pagerOnly = true) { + const sitesNumber = Math.ceil(data.totalRecordsNumber / data.rowsOnSite); + const currentSite = Number(Object.fromEntries(data.url.searchParams.entries())[site]); + const columnOptionsSelectId = 'columns-option-select-id'; + + const pageButton = (targetSite) => h(`button.btn${targetSite === currentSite ? '.btn-primary' : '.btn-secondary'}.no-text-decoration`, { + onclick: () => model.fetchedData.changePage(targetSite), + }, targetSite); + + const siteChangingController = (targetSite, content) => h('button.btn.btn-secondary.site-changing-controller', { + onclick: () => model.fetchedData.changePage(targetSite), + }, content); + + const moreSitesLeft = currentSite > 2; + const moreSitesRight = currentSite < sitesNumber - 1; + + function handleOptionChange() { + const columnsOptionsSelect = document.getElementById(columnOptionsSelectId); + const selectedValue = columnsOptionsSelect.options[columnsOptionsSelect.selectedIndex].value; + switch (selectedValue) { + case '0': + /* Show non empty columns */ + data.fields.forEach((f) => { + model.fetchedData.changeItemStatus( + f, + data.rows.some((r) => r[f.name]), + ); + model.notify(); + }); + break; + case '1': + /* Show all columns */ + for (const field of data.fields) { + field.marked = true; + } + model.notify(); + break; + case '2': + /* Customize */ + break; + default: + break; + } + } + + return [ + h('.flex-row.pager-panel.items-center', [ + pagerOnly + ? '' + : [ + h('.flex-wrap.justify-between.items-center', + h('.flex-wrap.justify-between.items-center.ph3', + h('.italic', itemsCounter(data))), + + h('button.btn.icon-only-button.m-right-15', { + className: model.sortingRowVisible ? 'btn-primary' : 'btn-secondary', + onclick: () => model.changeSortingRowVisibility(), + }, model.sortingRowVisible ? h('.sorting-20-off-white.abs-center') : h('.sorting-20-primary.abs-center')), + + h('select.select.column-options-select', { + id: columnOptionsSelectId, + name: columnOptionsSelectId, + onchange: () => handleOptionChange(), + }, + [ + h('option', { value: 0 }, 'Non empty columns'), + h('option', { value: 1 }, 'All columns'), + h('option', { value: 2 }, 'Customize'), + ], iconChevronBottom())), + ], + + h('.flex.pager-buttons', + // Move to the first site + currentSite > 1 ? siteChangingController(1, h('.double-left-15-primary')) : ' ', + // Move one site back + currentSite > 1 ? siteChangingController(currentSite - 1, h('.back-15-primary')) : ' ', + + // Move to the middle of sites range [first, current] + moreSitesLeft + ? siteChangingController( + Math.floor(currentSite / 2), + h('.more-15-primary'), + ) + : '', + + currentSite > 1 ? pageButton(currentSite - 1) : '', + pageButton(currentSite), + currentSite < sitesNumber ? pageButton(currentSite + 1) : '', + + // Move to the middle of sites range [current, last] + moreSitesRight + ? siteChangingController( + currentSite + Math.floor((sitesNumber - currentSite) / 2), + h('.more-15-primary'), + ) + : '', + + // Move one site forward + currentSite < sitesNumber + ? siteChangingController( + currentSite + 1, + h('.forward-15-primary'), + ) + : '', + + // Move to the last site + currentSite < sitesNumber + ? siteChangingController( + sitesNumber, + h('.double-right-15-primary'), + ) + : ''), + ]), + ]; +} diff --git a/app/public/components/table/pager.js b/app/public/components/table/pager.js index f51484a64..335be5406 100644 --- a/app/public/components/table/pager.js +++ b/app/public/components/table/pager.js @@ -13,22 +13,24 @@ */ import { h, iconChevronBottom } from '/js/src/index.js'; -import itemsCounter from '../../views/userView/data/table/items-counter.js'; // -import { RCT } from '../../config.js'; +import newItemsCounter from './newItemsCounter.js'; -const { site } = RCT.dataReqParams; +const columnDisplayOptions = { + nonEmpty: 'nonEmty', + all: 'all', +}; -export default function pager(model, data, pagerOnly = true) { - const sitesNumber = Math.ceil(data.totalRecordsNumber / data.rowsOnSite); - const currentSite = Number(Object.fromEntries(data.url.searchParams.entries())[site]); +export default function pager(periodsModel, model) { const columnOptionsSelectId = 'columns-option-select-id'; + const currentSite = periodsModel.pagination.currentPage; + const sitesNumber = periodsModel.pagination.pagesCount; const pageButton = (targetSite) => h(`button.btn${targetSite === currentSite ? '.btn-primary' : '.btn-secondary'}.no-text-decoration`, { - onclick: () => model.fetchedData.changePage(targetSite), + onclick: () => periodsModel.pagination.goToPage(targetSite), }, targetSite); const siteChangingController = (targetSite, content) => h('button.btn.btn-secondary.site-changing-controller', { - onclick: () => model.fetchedData.changePage(targetSite), + onclick: () => periodsModel.pagination.goToPage(targetSite), }, content); const moreSitesLeft = currentSite > 2; @@ -38,98 +40,84 @@ export default function pager(model, data, pagerOnly = true) { const columnsOptionsSelect = document.getElementById(columnOptionsSelectId); const selectedValue = columnsOptionsSelect.options[columnsOptionsSelect.selectedIndex].value; switch (selectedValue) { - case '0': - /* Show non empty columns */ - data.fields.forEach((f) => { - model.fetchedData.changeItemStatus( - f, - data.rows.some((r) => r[f.name]), - ); - model.notify(); + case columnDisplayOptions.nonEmpty: + periodsModel.fields.forEach((field) => { + periodsModel.toggleFieldVisibility(field, periodsModel.currentPagePeriods.payload.some((p) => p[field.name])); }); break; - case '1': - /* Show all columns */ - for (const field of data.fields) { - field.marked = true; + case columnDisplayOptions.all: + for (const field of periodsModel.fields) { + periodsModel.toggleFieldVisibility(field, true); } model.notify(); break; - case '2': - /* Customize */ - break; default: break; } } - return [ - h('.flex-row.pager-panel.items-center', [ - pagerOnly - ? '' - : [ - h('.flex-wrap.justify-between.items-center', - h('.flex-wrap.justify-between.items-center.ph3', - h('.italic', itemsCounter(data))), - - h('button.btn.icon-only-button.m-right-15', { - className: model.sortingRowVisible ? 'btn-primary' : 'btn-secondary', - onclick: () => model.changeSortingRowVisibility(), - }, model.sortingRowVisible ? h('.sorting-20-off-white.abs-center') : h('.sorting-20-primary.abs-center')), - - h('select.select.column-options-select', { - id: columnOptionsSelectId, - name: columnOptionsSelectId, - onchange: () => handleOptionChange(), - }, - [ - h('option', { value: 0 }, 'Non empty columns'), - h('option', { value: 1 }, 'All columns'), - h('option', { value: 2 }, 'Customize'), - ], iconChevronBottom())), - ], - - h('.flex.pager-buttons', - // Move to the first site - currentSite > 1 ? siteChangingController(1, h('.double-left-15-primary')) : ' ', - // Move one site back - currentSite > 1 ? siteChangingController(currentSite - 1, h('.back-15-primary')) : ' ', - - // Move to the middle of sites range [first, current] - moreSitesLeft - ? siteChangingController( - Math.floor(currentSite / 2), - h('.more-15-primary'), - ) - : '', - - currentSite > 1 ? pageButton(currentSite - 1) : '', - pageButton(currentSite), - currentSite < sitesNumber ? pageButton(currentSite + 1) : '', - - // Move to the middle of sites range [current, last] - moreSitesRight - ? siteChangingController( - currentSite + Math.floor((sitesNumber - currentSite) / 2), - h('.more-15-primary'), - ) - : '', - - // Move one site forward - currentSite < sitesNumber - ? siteChangingController( - currentSite + 1, - h('.forward-15-primary'), - ) - : '', - - // Move to the last site - currentSite < sitesNumber - ? siteChangingController( - sitesNumber, - h('.double-right-15-primary'), - ) - : ''), - ]), - ]; + return h('.flex-row.pager-panel.items-center', [ + h('.flex-wrap.justify-between.items-center', + h('.flex-wrap.justify-between.items-center.ph3', + h('.italic', newItemsCounter(periodsModel.pagination))), + + h('button.btn.icon-only-button.m-right-15', { + className: periodsModel.sortingRowVisible ? 'btn-primary' : 'btn-secondary', + onclick: () => periodsModel.toggleSortingRowVisibility(), + }, periodsModel.sortingRowVisible ? h('.sorting-20-off-white.abs-center') : h('.sorting-20-primary.abs-center')), + + h('select.select.column-options-select', { + id: columnOptionsSelectId, + name: columnOptionsSelectId, + onchange: () => handleOptionChange(), + }, + [ + h('option', { value: columnDisplayOptions.nonEmpty }, 'Non empty columns'), + h('option', { value: columnDisplayOptions.all }, 'All columns'), + // ToDo add customizable option => open modal here + ], iconChevronBottom())), + + h('.flex.pager-buttons', + // Move to the first site + currentSite > 1 ? siteChangingController(1, h('.double-left-15-primary')) : ' ', + // Move one site back + currentSite > 1 ? siteChangingController(currentSite - 1, h('.back-15-primary')) : ' ', + + // Move to the middle of sites range [first, current] + moreSitesLeft + ? siteChangingController( + Math.floor(currentSite / 2), + h('.more-15-primary'), + ) + : '', + + currentSite > 1 ? pageButton(currentSite - 1) : '', + pageButton(currentSite), + currentSite < sitesNumber ? pageButton(currentSite + 1) : '', + + // Move to the middle of sites range [current, last] + moreSitesRight + ? siteChangingController( + currentSite + Math.floor((sitesNumber - currentSite) / 2), + h('.more-15-primary'), + ) + : '', + + // Move one site forward + currentSite < sitesNumber + ? siteChangingController( + currentSite + 1, + h('.forward-15-primary'), + ) + : '', + + // Move to the last site + currentSite < sitesNumber + ? siteChangingController( + sitesNumber, + h('.double-right-15-primary'), + ) + : ''), + + ]); } diff --git a/app/public/views/periods/overview/periodsContent.js b/app/public/views/periods/overview/periodsContent.js index 74a349fa4..533b9bfe3 100644 --- a/app/public/views/periods/overview/periodsContent.js +++ b/app/public/views/periods/overview/periodsContent.js @@ -21,7 +21,7 @@ import activeFilters from '../../userView/data/table/filtering/activeFilters.js' import { noDataFound, noMatchingData } from '../../../components/messagePanel/messages.js'; import periodsTableHeader from '../table/periodsTableHeader.js'; import periodsTableRow from '../table/periodsTableRow.js'; -import newPager from '../../../components/table/newPager.js'; +import pager from '../../../components/table/pager.js'; const pageName = RCT.pageNames.periods; /** @@ -57,7 +57,7 @@ export default function periodsContent(periodsModel, periods, model) { ? periodsModel.visibleFields.length > 0 ? h('.p-top-05em', h('.x-scrollable-table.border-sh', - newPager(periodsModel, model), + pager(periodsModel, model), h(`table.${pageName}-table`, { id: `data-table-${pageName}`, }, diff --git a/app/public/views/runs/runsPerDataPass/overview/content.js b/app/public/views/runs/runsPerDataPass/overview/content.js index 579de5d48..9420bfcd1 100644 --- a/app/public/views/runs/runsPerDataPass/overview/content.js +++ b/app/public/views/runs/runsPerDataPass/overview/content.js @@ -14,7 +14,7 @@ import { h } from '/js/src/index.js'; import indexChip from '../../../../components/chips/indexChip.js'; -import pager from '../../../../components/table/pager.js'; +import obsoletePager from '../../../../components/table/obsoletePager.js'; import { defaultIndexString } from '../../../../utils/defaults.js'; import { anyFiltersActive } from '../../../../utils/filtering/filterUtils.js'; import pagesCellsSpecials from '../../../userView/data/pagesCellsSpecials.js'; @@ -71,14 +71,14 @@ export default function content(model, runs, detectors) { ? visibleFields.length > 0 ? h('.p-top-05em', h('.x-scrollable-table.border-sh', - pager(model, data, false), // + obsoletePager(model, data, false), // h('table.runs-table', { id: `data-table-${data.url}`, }, header(visibleFields, data, model), model.sortingRowVisible ? sortingRow(visibleFields, data, model) : '', tableBody(model, visibleFields, data, cellsSpecials, dataPointer.index, runs, detectors)), - data.rows.length > 15 ? pager(model, data) : '')) + data.rows.length > 15 ? obsoletePager(model, data) : '')) : '' : anyFiltersActive(url) ? noMatchingData(model, dataPointer.page) diff --git a/app/public/views/runs/runsPerPeriod/overview/content.js b/app/public/views/runs/runsPerPeriod/overview/content.js index 1790370df..eda7c1785 100644 --- a/app/public/views/runs/runsPerPeriod/overview/content.js +++ b/app/public/views/runs/runsPerPeriod/overview/content.js @@ -14,7 +14,7 @@ import { h } from '/js/src/index.js'; import indexChip from '../../../../components/chips/indexChip.js'; -import pager from '../../../../components/table/pager.js'; +import obsoletePager from '../../../../components/table/obsoletePager.js'; import { defaultIndexString } from '../../../../utils/defaults.js'; import { anyFiltersActive } from '../../../../utils/filtering/filterUtils.js'; import pagesCellsSpecials from '../../../userView/data/pagesCellsSpecials.js'; @@ -68,14 +68,14 @@ export default function content(model, runs, detectors) { ? visibleFields.length > 0 ? h('.p-top-05em', h('.x-scrollable-table.border-sh', - pager(model, data, false), + obsoletePager(model, data, false), h('table.runs-table', { id: `data-table-${url}`, }, header(visibleFields, data, model), model.sortingRowVisible ? sortingRow(visibleFields, data, model) : '', tableBody(model, visibleFields, data, cellsSpecials, dataPointer.index, runs, detectors)), - data.rows.length > 15 ? pager(model, data) : '')) + data.rows.length > 15 ? obsoletePager(model, data) : '')) : '' : anyFiltersActive(url) ? noMatchingData(model, dataPointer.page) diff --git a/app/public/views/userView/data/table/tablePanel.js b/app/public/views/userView/data/table/tablePanel.js index ef19327be..689f131d8 100644 --- a/app/public/views/userView/data/table/tablePanel.js +++ b/app/public/views/userView/data/table/tablePanel.js @@ -16,7 +16,7 @@ import { h } from '/js/src/index.js'; import tableHeader from './header.js'; import row from './row.js'; import pagesCellsSpecials from '../pagesCellsSpecials.js'; -import pager from '../../../../components/table/pager.js'; +import obsoletePager from '../../../../components/table/obsoletePager.js'; import filter from './filtering/filter.js'; import activeFilters from './filtering/activeFilters.js'; @@ -79,14 +79,14 @@ export default function tablePanel(model, runs) { ? visibleFields.length > 0 ? h('.p-top-05em', h('.x-scrollable-table.border-sh', - pager(model, data, false), + obsoletePager(model, data, false), h(`table.${dataPointer.page}-table`, { id: `data-table-${data.url}`, }, tableHeader(visibleFields, data, model), model.sortingRowVisible ? sortingRow(visibleFields, data, model) : '', tableBody(model, visibleFields, data, cellsSpecials, runs)), - data.rows.length > 15 ? pager(model, data) : '')) + data.rows.length > 15 ? obsoletePager(model, data) : '')) : '' : anyFiltersActive(url) ? noMatchingData(model, dataPointer.page) From 5e0fc39a2cb4b7f405cd8051a06132a4276ed771 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 16 Aug 2023 13:31:16 +0200 Subject: [PATCH 20/82] Remove the old period field name config --- app/config/rct-data/viewFieldNames/index.js | 2 -- .../viewFieldNames/oldPeriodFieldNames.js | 35 ------------------- 2 files changed, 37 deletions(-) delete mode 100644 app/config/rct-data/viewFieldNames/oldPeriodFieldNames.js diff --git a/app/config/rct-data/viewFieldNames/index.js b/app/config/rct-data/viewFieldNames/index.js index a038c2e9c..41acc8f01 100644 --- a/app/config/rct-data/viewFieldNames/index.js +++ b/app/config/rct-data/viewFieldNames/index.js @@ -12,14 +12,12 @@ * or submit itself to any jurisdiction. */ -const oldPeriodFieldNames = require('./oldPeriodFieldNames.js'); const periodFieldNames = require('./periodFieldNames.js'); const runFieldNames = require('./runFieldNames.js'); const dataPassFieldNames = require('./dataPassFieldNames.js'); const mcFieldNames = require('./mcFieldNames.js'); module.exports = { - oldPeriodFieldNames, periodFieldNames, runFieldNames, dataPassFieldNames, diff --git a/app/config/rct-data/viewFieldNames/oldPeriodFieldNames.js b/app/config/rct-data/viewFieldNames/oldPeriodFieldNames.js deleted file mode 100644 index 86a5238d0..000000000 --- a/app/config/rct-data/viewFieldNames/oldPeriodFieldNames.js +++ /dev/null @@ -1,35 +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. - */ -const { filterInputTypes } = require('../filterTypes.js'); - -const oldPeriodFieldNames = { - name: { - fieldName: 'Name', - filterInput: filterInputTypes.text, - }, - year: { - fieldName: 'Year', - filterInput: filterInputTypes.number, - }, - beam: { - fieldName: 'Beam', - filterInput: filterInputTypes.text, - }, - energy: { - fieldName: 'Mean energy [GeV]', - filterInput: filterInputTypes.number, - }, -}; - -module.exports = oldPeriodFieldNames; From dc1812213bd534fe70760fb3dc3bb96895cc13c9 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 16 Aug 2023 13:35:14 +0200 Subject: [PATCH 21/82] Mark previous items couter as obsolete --- app/public/components/table/obsoletePager.js | 4 ++-- .../data/table/{items-counter.js => obsoleteItemsCounter.js} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename app/public/views/userView/data/table/{items-counter.js => obsoleteItemsCounter.js} (95%) diff --git a/app/public/components/table/obsoletePager.js b/app/public/components/table/obsoletePager.js index dc2174239..790bf44ba 100644 --- a/app/public/components/table/obsoletePager.js +++ b/app/public/components/table/obsoletePager.js @@ -13,7 +13,7 @@ */ import { h, iconChevronBottom } from '/js/src/index.js'; -import itemsCounter from '../../views/userView/data/table/items-counter.js'; // +import obsoleteItemsCounter from '../../views/userView/data/table/obsoleteItemsCounter.js'; import { RCT } from '../../config.js'; const { site } = RCT.dataReqParams; @@ -70,7 +70,7 @@ export default function obsoletePager(model, data, pagerOnly = true) { : [ h('.flex-wrap.justify-between.items-center', h('.flex-wrap.justify-between.items-center.ph3', - h('.italic', itemsCounter(data))), + h('.italic', obsoleteItemsCounter(data))), h('button.btn.icon-only-button.m-right-15', { className: model.sortingRowVisible ? 'btn-primary' : 'btn-secondary', diff --git a/app/public/views/userView/data/table/items-counter.js b/app/public/views/userView/data/table/obsoleteItemsCounter.js similarity index 95% rename from app/public/views/userView/data/table/items-counter.js rename to app/public/views/userView/data/table/obsoleteItemsCounter.js index b0d8e4f41..3f58b4b6e 100644 --- a/app/public/views/userView/data/table/items-counter.js +++ b/app/public/views/userView/data/table/obsoleteItemsCounter.js @@ -16,7 +16,7 @@ import { RCT } from '../../../../config.js'; const siteParamName = RCT.dataReqParams.site; -export default function itemsCounter(data) { +export default function obsoleteItemsCounter(data) { const currentSite = Number(Object.fromEntries(data.url.searchParams.entries())[siteParamName]); const firstRowIdx = (currentSite - 1) * data.rowsOnSite + 1; From b3060f97d78c2f6bff88e031b0a2c4867054a8cd Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 16 Aug 2023 13:37:01 +0200 Subject: [PATCH 22/82] Use the new items counter in the periods view --- .../components/table/{newItemsCounter.js => itemsCounter.js} | 2 +- app/public/components/table/pager.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename app/public/components/table/{newItemsCounter.js => itemsCounter.js} (94%) diff --git a/app/public/components/table/newItemsCounter.js b/app/public/components/table/itemsCounter.js similarity index 94% rename from app/public/components/table/newItemsCounter.js rename to app/public/components/table/itemsCounter.js index d11cf0fad..1958f2171 100644 --- a/app/public/components/table/newItemsCounter.js +++ b/app/public/components/table/itemsCounter.js @@ -12,7 +12,7 @@ * or submit itself to any jurisdiction. */ -export default function newItemsCounter(paginationModel) { +export default function itemsCounter(paginationModel) { const currentSite = paginationModel.currentPage; const firstRowIdx = (currentSite - 1) * paginationModel.itemsPerPage + 1; diff --git a/app/public/components/table/pager.js b/app/public/components/table/pager.js index 335be5406..fdfc526c2 100644 --- a/app/public/components/table/pager.js +++ b/app/public/components/table/pager.js @@ -13,7 +13,7 @@ */ import { h, iconChevronBottom } from '/js/src/index.js'; -import newItemsCounter from './newItemsCounter.js'; +import itemsCounter from './itemsCounter.js'; const columnDisplayOptions = { nonEmpty: 'nonEmty', @@ -59,7 +59,7 @@ export default function pager(periodsModel, model) { return h('.flex-row.pager-panel.items-center', [ h('.flex-wrap.justify-between.items-center', h('.flex-wrap.justify-between.items-center.ph3', - h('.italic', newItemsCounter(periodsModel.pagination))), + h('.italic', itemsCounter(periodsModel.pagination))), h('button.btn.icon-only-button.m-right-15', { className: periodsModel.sortingRowVisible ? 'btn-primary' : 'btn-secondary', From c879e256a77670c190251a181acca7ce6f0d258c Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 16 Aug 2023 13:38:37 +0200 Subject: [PATCH 23/82] Docs cleanup :broom: --- app/public/Model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/public/Model.js b/app/public/Model.js index 4c8ea4bf8..715c8016d 100644 --- a/app/public/Model.js +++ b/app/public/Model.js @@ -63,7 +63,7 @@ export default class Model extends Observable { /** * Delegates sub-model actions depending on new location of the page - * @returns {vnode} The page to be loaded + * @returns {void} */ async handleLocationChange() { switch (this.router.params.page) { From 5b565e360dc0aa6853c1bb77d0a8d0169450be0f Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 16 Aug 2023 13:46:06 +0200 Subject: [PATCH 24/82] Rename column display options --- app/public/components/table/pager.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/public/components/table/pager.js b/app/public/components/table/pager.js index fdfc526c2..61b5b4343 100644 --- a/app/public/components/table/pager.js +++ b/app/public/components/table/pager.js @@ -21,7 +21,7 @@ const columnDisplayOptions = { }; export default function pager(periodsModel, model) { - const columnOptionsSelectId = 'columns-option-select-id'; + const columnDisplayOptionsSelectId = 'columns-option-select-id'; const currentSite = periodsModel.pagination.currentPage; const sitesNumber = periodsModel.pagination.pagesCount; @@ -36,9 +36,9 @@ export default function pager(periodsModel, model) { const moreSitesLeft = currentSite > 2; const moreSitesRight = currentSite < sitesNumber - 1; - function handleOptionChange() { - const columnsOptionsSelect = document.getElementById(columnOptionsSelectId); - const selectedValue = columnsOptionsSelect.options[columnsOptionsSelect.selectedIndex].value; + function handleColumnOptionDisplayChange() { + const columnOptionsSelect = document.getElementById(columnDisplayOptionsSelectId); + const selectedValue = columnOptionsSelect.options[columnOptionsSelect.selectedIndex].value; switch (selectedValue) { case columnDisplayOptions.nonEmpty: periodsModel.fields.forEach((field) => { @@ -67,9 +67,9 @@ export default function pager(periodsModel, model) { }, periodsModel.sortingRowVisible ? h('.sorting-20-off-white.abs-center') : h('.sorting-20-primary.abs-center')), h('select.select.column-options-select', { - id: columnOptionsSelectId, - name: columnOptionsSelectId, - onchange: () => handleOptionChange(), + id: columnDisplayOptionsSelectId, + name: columnDisplayOptionsSelectId, + onchange: () => handleColumnOptionDisplayChange(), }, [ h('option', { value: columnDisplayOptions.nonEmpty }, 'Non empty columns'), From 4b90f2133ffbad3cced6024e3085d342b0108bff Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 16 Aug 2023 13:53:38 +0200 Subject: [PATCH 25/82] Rename css class --- app/public/components/table/obsoletePager.js | 2 +- app/public/components/table/pager.js | 6 +++--- app/public/styles/custom.less | 4 ++++ app/public/styles/rct/custom/components/pager.css | 3 --- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/public/components/table/obsoletePager.js b/app/public/components/table/obsoletePager.js index 790bf44ba..e53851ac7 100644 --- a/app/public/components/table/obsoletePager.js +++ b/app/public/components/table/obsoletePager.js @@ -89,7 +89,7 @@ export default function obsoletePager(model, data, pagerOnly = true) { ], iconChevronBottom())), ], - h('.flex.pager-buttons', + h('.flex.m-right-0-3-rem', // Move to the first site currentSite > 1 ? siteChangingController(1, h('.double-left-15-primary')) : ' ', // Move one site back diff --git a/app/public/components/table/pager.js b/app/public/components/table/pager.js index 61b5b4343..ed9971d9c 100644 --- a/app/public/components/table/pager.js +++ b/app/public/components/table/pager.js @@ -38,8 +38,8 @@ export default function pager(periodsModel, model) { function handleColumnOptionDisplayChange() { const columnOptionsSelect = document.getElementById(columnDisplayOptionsSelectId); - const selectedValue = columnOptionsSelect.options[columnOptionsSelect.selectedIndex].value; - switch (selectedValue) { + const selectedOption = columnOptionsSelect.options[columnOptionsSelect.selectedIndex].value; + switch (selectedOption) { case columnDisplayOptions.nonEmpty: periodsModel.fields.forEach((field) => { periodsModel.toggleFieldVisibility(field, periodsModel.currentPagePeriods.payload.some((p) => p[field.name])); @@ -77,7 +77,7 @@ export default function pager(periodsModel, model) { // ToDo add customizable option => open modal here ], iconChevronBottom())), - h('.flex.pager-buttons', + h('.flex.m-right-0-3-rem', // Move to the first site currentSite > 1 ? siteChangingController(1, h('.double-left-15-primary')) : ' ', // Move one site back diff --git a/app/public/styles/custom.less b/app/public/styles/custom.less index a734e4399..9eacbe476 100644 --- a/app/public/styles/custom.less +++ b/app/public/styles/custom.less @@ -125,6 +125,10 @@ margin: 21px !important; } +.m-right-0-3-rem { + margin-right: 0.3rem !important; +} + .m-right-15 { margin-right: 15px !important; } diff --git a/app/public/styles/rct/custom/components/pager.css b/app/public/styles/rct/custom/components/pager.css index 36ce4259a..8c258c03c 100644 --- a/app/public/styles/rct/custom/components/pager.css +++ b/app/public/styles/rct/custom/components/pager.css @@ -42,6 +42,3 @@ cursor: pointer; } -.rct .pager-buttons { - margin-right: 10px; -} From 9ee617af967a4ec1c6d45bd9f460a99fd6d38a09 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 16 Aug 2023 13:55:14 +0200 Subject: [PATCH 26/82] Rename column display options select --- app/public/components/table/obsoletePager.js | 2 +- app/public/components/table/pager.js | 2 +- app/public/styles/rct/custom/input/select.css | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/public/components/table/obsoletePager.js b/app/public/components/table/obsoletePager.js index e53851ac7..21cb11d13 100644 --- a/app/public/components/table/obsoletePager.js +++ b/app/public/components/table/obsoletePager.js @@ -77,7 +77,7 @@ export default function obsoletePager(model, data, pagerOnly = true) { onclick: () => model.changeSortingRowVisibility(), }, model.sortingRowVisible ? h('.sorting-20-off-white.abs-center') : h('.sorting-20-primary.abs-center')), - h('select.select.column-options-select', { + h('select.select.column-display-options-select', { id: columnOptionsSelectId, name: columnOptionsSelectId, onchange: () => handleOptionChange(), diff --git a/app/public/components/table/pager.js b/app/public/components/table/pager.js index ed9971d9c..ebd29454c 100644 --- a/app/public/components/table/pager.js +++ b/app/public/components/table/pager.js @@ -66,7 +66,7 @@ export default function pager(periodsModel, model) { onclick: () => periodsModel.toggleSortingRowVisibility(), }, periodsModel.sortingRowVisible ? h('.sorting-20-off-white.abs-center') : h('.sorting-20-primary.abs-center')), - h('select.select.column-options-select', { + h('select.select.column-display-options-select', { id: columnDisplayOptionsSelectId, name: columnDisplayOptionsSelectId, onchange: () => handleColumnOptionDisplayChange(), diff --git a/app/public/styles/rct/custom/input/select.css b/app/public/styles/rct/custom/input/select.css index f45870ae8..8e1f90d8b 100644 --- a/app/public/styles/rct/custom/input/select.css +++ b/app/public/styles/rct/custom/input/select.css @@ -32,7 +32,7 @@ padding-right: 30px; } -.rct .select.column-options-select { +.rct .select.column-display-options-select { min-width: 170px; } From 7f66310d035d74ed86e7679504fbbd8a02a00d1c Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 16 Aug 2023 14:00:48 +0200 Subject: [PATCH 27/82] Cleanup :broom: --- app/public/components/table/obsoletePager.js | 4 ---- app/public/components/table/pager.js | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/app/public/components/table/obsoletePager.js b/app/public/components/table/obsoletePager.js index 21cb11d13..c26737ac5 100644 --- a/app/public/components/table/obsoletePager.js +++ b/app/public/components/table/obsoletePager.js @@ -55,9 +55,6 @@ export default function obsoletePager(model, data, pagerOnly = true) { } model.notify(); break; - case '2': - /* Customize */ - break; default: break; } @@ -85,7 +82,6 @@ export default function obsoletePager(model, data, pagerOnly = true) { [ h('option', { value: 0 }, 'Non empty columns'), h('option', { value: 1 }, 'All columns'), - h('option', { value: 2 }, 'Customize'), ], iconChevronBottom())), ], diff --git a/app/public/components/table/pager.js b/app/public/components/table/pager.js index ebd29454c..6753f202c 100644 --- a/app/public/components/table/pager.js +++ b/app/public/components/table/pager.js @@ -79,9 +79,9 @@ export default function pager(periodsModel, model) { h('.flex.m-right-0-3-rem', // Move to the first site - currentSite > 1 ? siteChangingController(1, h('.double-left-15-primary')) : ' ', + currentSite > 1 ? siteChangingController(1, h('.double-left-15-primary')) : '', // Move one site back - currentSite > 1 ? siteChangingController(currentSite - 1, h('.back-15-primary')) : ' ', + currentSite > 1 ? siteChangingController(currentSite - 1, h('.back-15-primary')) : '', // Move to the middle of sites range [first, current] moreSitesLeft From e27b34531dbac9ee7de2110c0c914deb1ed793fb Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 16 Aug 2023 14:32:37 +0200 Subject: [PATCH 28/82] Use the tableManager row --- app/public/Model.js | 2 +- app/public/components/table/pageSelector.js | 74 +++++++++++++++++++ .../table/pagination/PaginationModel.js | 11 +++ .../table/{pager.js => tableManager.js} | 64 +++------------- .../periods/{Periods.js => PeriodsModel.js} | 1 + .../views/periods/overview/periodsContent.js | 4 +- 6 files changed, 98 insertions(+), 58 deletions(-) create mode 100644 app/public/components/table/pageSelector.js rename app/public/components/table/{pager.js => tableManager.js} (55%) rename app/public/views/periods/{Periods.js => PeriodsModel.js} (98%) diff --git a/app/public/Model.js b/app/public/Model.js index 715c8016d..7b73d1506 100644 --- a/app/public/Model.js +++ b/app/public/Model.js @@ -18,7 +18,7 @@ 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'; -import PeriodsModel from './views/periods/Periods.js'; +import PeriodsModel from './views/periods/PeriodsModel.js'; import UserPreferences from './model/UserPreferences.js'; const { roles, dataAccess, pageNames } = RCT; diff --git a/app/public/components/table/pageSelector.js b/app/public/components/table/pageSelector.js new file mode 100644 index 000000000..14af04f3b --- /dev/null +++ b/app/public/components/table/pageSelector.js @@ -0,0 +1,74 @@ +/** + * @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'; + +export default function pageSelector(currentPage, pagesCount, onPageChange) { + const pageButtonStyle = (targetPage) => targetPage === currentPage + ? '.btn-primary' + : '.btn-secondary'; + + const pageNumberButton = (targetPage) => h(`button.btn${pageButtonStyle(targetPage)}.no-text-decoration`, { + onclick: () => onPageChange(targetPage), + }, targetPage); + + const pageIconButton = (targetPage, content) => h('button.btn.btn-secondary.site-changing-controller', { + onclick: () => onPageChange(targetPage), + }, content); + + const morePagesLeft = currentPage > 2; + const morePagesRight = currentPage < pagesCount - 1; + + return h('.flex.m-right-0-3-rem', + // Move to the first page + currentPage > 1 ? pageIconButton(1, h('.double-left-15-primary')) : '', + // Move one site back + currentPage > 1 ? pageIconButton(currentPage - 1, h('.back-15-primary')) : '', + + // Move to the middle of sites range [first, current] + morePagesLeft + ? pageIconButton( + Math.floor(currentPage / 2), + h('.more-15-primary'), + ) + : '', + + currentPage > 1 ? pageNumberButton(currentPage - 1) : '', + pageNumberButton(currentPage), + currentPage < pagesCount ? pageNumberButton(currentPage + 1) : '', + + // Move to the middle of sites range [current, last] + morePagesRight + ? pageIconButton( + currentPage + Math.floor((pagesCount - currentPage) / 2), + h('.more-15-primary'), + ) + : '', + + // Move one site forward + currentPage < pagesCount + ? pageIconButton( + currentPage + 1, + h('.forward-15-primary'), + ) + : '', + + // Move to the last site + currentPage < pagesCount + ? pageIconButton( + pagesCount, + h('.double-right-15-primary'), + ) + : ''); +} diff --git a/app/public/components/table/pagination/PaginationModel.js b/app/public/components/table/pagination/PaginationModel.js index 94bb6cd46..016a90ac8 100644 --- a/app/public/components/table/pagination/PaginationModel.js +++ b/app/public/components/table/pagination/PaginationModel.js @@ -34,6 +34,8 @@ export class PaginationModel extends Observable { this._currentPage = DEFAULT_CURRENT_PAGE; this._itemsCount = DEFAULT_ITEMS_COUNT; this._isInfiniteScrollEnabled = ENABLE_INFINITE_MODE_BY_DEFAULT; + + this._itemsPerPageSelector$ = new Observable(); } /** @@ -188,4 +190,13 @@ export class PaginationModel extends Observable { this._currentPage = DEFAULT_CURRENT_PAGE; this.notify(); } + + /** + * Observable notified when the item per page selector change (either a custom value is typed, or its visibility change) + * + * @return {Observable} the selector observable + */ + get itemsPerPageSelector$() { + return this._itemsPerPageSelector$; + } } diff --git a/app/public/components/table/pager.js b/app/public/components/table/tableManager.js similarity index 55% rename from app/public/components/table/pager.js rename to app/public/components/table/tableManager.js index 6753f202c..dd851443f 100644 --- a/app/public/components/table/pager.js +++ b/app/public/components/table/tableManager.js @@ -14,27 +14,15 @@ import { h, iconChevronBottom } from '/js/src/index.js'; import itemsCounter from './itemsCounter.js'; +import pageSelector from './pageSelector.js'; const columnDisplayOptions = { nonEmpty: 'nonEmty', all: 'all', }; -export default function pager(periodsModel, model) { +export default function tableManager(periodsModel, model) { const columnDisplayOptionsSelectId = 'columns-option-select-id'; - const currentSite = periodsModel.pagination.currentPage; - const sitesNumber = periodsModel.pagination.pagesCount; - - const pageButton = (targetSite) => h(`button.btn${targetSite === currentSite ? '.btn-primary' : '.btn-secondary'}.no-text-decoration`, { - onclick: () => periodsModel.pagination.goToPage(targetSite), - }, targetSite); - - const siteChangingController = (targetSite, content) => h('button.btn.btn-secondary.site-changing-controller', { - onclick: () => periodsModel.pagination.goToPage(targetSite), - }, content); - - const moreSitesLeft = currentSite > 2; - const moreSitesRight = currentSite < sitesNumber - 1; function handleColumnOptionDisplayChange() { const columnOptionsSelect = document.getElementById(columnDisplayOptionsSelectId); @@ -77,47 +65,13 @@ export default function pager(periodsModel, model) { // ToDo add customizable option => open modal here ], iconChevronBottom())), - h('.flex.m-right-0-3-rem', - // Move to the first site - currentSite > 1 ? siteChangingController(1, h('.double-left-15-primary')) : '', - // Move one site back - currentSite > 1 ? siteChangingController(currentSite - 1, h('.back-15-primary')) : '', - - // Move to the middle of sites range [first, current] - moreSitesLeft - ? siteChangingController( - Math.floor(currentSite / 2), - h('.more-15-primary'), - ) - : '', - - currentSite > 1 ? pageButton(currentSite - 1) : '', - pageButton(currentSite), - currentSite < sitesNumber ? pageButton(currentSite + 1) : '', - - // Move to the middle of sites range [current, last] - moreSitesRight - ? siteChangingController( - currentSite + Math.floor((sitesNumber - currentSite) / 2), - h('.more-15-primary'), - ) - : '', - - // Move one site forward - currentSite < sitesNumber - ? siteChangingController( - currentSite + 1, - h('.forward-15-primary'), - ) - : '', - - // Move to the last site - currentSite < sitesNumber - ? siteChangingController( - sitesNumber, - h('.double-right-15-primary'), - ) - : ''), + pageSelector( + periodsModel.pagination.currentPage, + periodsModel.pagination.pagesCount, + (page) => { + periodsModel.pagination.currentPage = page; + }, + ), ]); } diff --git a/app/public/views/periods/Periods.js b/app/public/views/periods/PeriodsModel.js similarity index 98% rename from app/public/views/periods/Periods.js rename to app/public/views/periods/PeriodsModel.js index 4203186d5..28572d788 100644 --- a/app/public/views/periods/Periods.js +++ b/app/public/views/periods/PeriodsModel.js @@ -33,6 +33,7 @@ export default class PeriodsModel extends Observable { this._pagination = new PaginationModel(model.userPreferences); this._pagination.observe(() => this.fetchAllPeriods()); + this._pagination.itemsPerPageSelector$.observe(() => this.notify()); this._fields = Object.keys(RCT.fieldNames.periods).map((field) => ({ name: field, visible: true })); diff --git a/app/public/views/periods/overview/periodsContent.js b/app/public/views/periods/overview/periodsContent.js index 533b9bfe3..e7a911438 100644 --- a/app/public/views/periods/overview/periodsContent.js +++ b/app/public/views/periods/overview/periodsContent.js @@ -21,7 +21,7 @@ import activeFilters from '../../userView/data/table/filtering/activeFilters.js' import { noDataFound, noMatchingData } from '../../../components/messagePanel/messages.js'; import periodsTableHeader from '../table/periodsTableHeader.js'; import periodsTableRow from '../table/periodsTableRow.js'; -import pager from '../../../components/table/pager.js'; +import tableManager from '../../../components/table/tableManager.js'; const pageName = RCT.pageNames.periods; /** @@ -57,7 +57,7 @@ export default function periodsContent(periodsModel, periods, model) { ? periodsModel.visibleFields.length > 0 ? h('.p-top-05em', h('.x-scrollable-table.border-sh', - pager(periodsModel, model), + tableManager(periodsModel, model), h(`table.${pageName}-table`, { id: `data-table-${pageName}`, }, From ae261717718661cc434ce63ad27bc32b464f1edf Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 16 Aug 2023 15:00:21 +0200 Subject: [PATCH 29/82] Fix setting rows on page --- .../components/table/pagination/PaginationModel.js | 14 ++++---------- app/public/model/UserPreferences.js | 8 ++++++++ app/public/views/periods/PeriodsModel.js | 5 +++-- .../userView/data/pageSettings/pageSettings.js | 2 +- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/app/public/components/table/pagination/PaginationModel.js b/app/public/components/table/pagination/PaginationModel.js index 016a90ac8..1450d8a13 100644 --- a/app/public/components/table/pagination/PaginationModel.js +++ b/app/public/components/table/pagination/PaginationModel.js @@ -35,7 +35,10 @@ export class PaginationModel extends Observable { this._itemsCount = DEFAULT_ITEMS_COUNT; this._isInfiniteScrollEnabled = ENABLE_INFINITE_MODE_BY_DEFAULT; - this._itemsPerPageSelector$ = new Observable(); + this._userPreferences.observe(() => { + this._itemsPerPage = this._userPreferences.rowsOnSite; + this.notify(); + }); } /** @@ -190,13 +193,4 @@ export class PaginationModel extends Observable { this._currentPage = DEFAULT_CURRENT_PAGE; this.notify(); } - - /** - * Observable notified when the item per page selector change (either a custom value is typed, or its visibility change) - * - * @return {Observable} the selector observable - */ - get itemsPerPageSelector$() { - return this._itemsPerPageSelector$; - } } diff --git a/app/public/model/UserPreferences.js b/app/public/model/UserPreferences.js index 86875cba2..bf54c9ad6 100644 --- a/app/public/model/UserPreferences.js +++ b/app/public/model/UserPreferences.js @@ -44,6 +44,14 @@ export default class UserPreferences extends Observable { const url = this.parent.router.getUrl(); const newUrl = replaceUrlParams(url, { [dataReqParams.rowsOnSite]: this.rowsOnSite }); this.parent.router.go(newUrl); + } // Obsolete + + setItemsPerPage(itemsPerPage) { + this.rowsOnSite = itemsPerPage; + this.notify(); + const url = this.parent.router.getUrl(); + const newUrl = replaceUrlParams(url, { [dataReqParams.rowsOnSite]: this.rowsOnSite }); + this.parent.router.go(newUrl); } setUiTheme(uiTheme) { diff --git a/app/public/views/periods/PeriodsModel.js b/app/public/views/periods/PeriodsModel.js index 28572d788..e852f374f 100644 --- a/app/public/views/periods/PeriodsModel.js +++ b/app/public/views/periods/PeriodsModel.js @@ -32,8 +32,9 @@ export default class PeriodsModel extends Observable { this.model = model; this._pagination = new PaginationModel(model.userPreferences); - this._pagination.observe(() => this.fetchAllPeriods()); - this._pagination.itemsPerPageSelector$.observe(() => this.notify()); + this._pagination.observe(() => { + this.fetchAllPeriods(); this.notify(); + }); this._fields = Object.keys(RCT.fieldNames.periods).map((field) => ({ name: field, visible: true })); diff --git a/app/public/views/userView/data/pageSettings/pageSettings.js b/app/public/views/userView/data/pageSettings/pageSettings.js index dbb4504a0..864a6981a 100644 --- a/app/public/views/userView/data/pageSettings/pageSettings.js +++ b/app/public/views/userView/data/pageSettings/pageSettings.js @@ -33,7 +33,7 @@ export default function pageSettings(userPreferences, close) { alert('incorrect number of rows on page: must be in range of [1, 200]'); input.value = userPreferences.rowsOnSite; } else { - userPreferences.setRowsOnSite(inputValue); + userPreferences.setItemsPerPage(inputValue); } close(); }; From 4311b94ec79feeff06ed2425805c7eeba83e4d9f Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 16 Aug 2023 15:11:55 +0200 Subject: [PATCH 30/82] Cleanup :broom: --- .../table/pagination/PaginationModel.js | 33 ++----------------- app/public/views/periods/PeriodsModel.js | 17 ++-------- 2 files changed, 5 insertions(+), 45 deletions(-) diff --git a/app/public/components/table/pagination/PaginationModel.js b/app/public/components/table/pagination/PaginationModel.js index 1450d8a13..e90b08848 100644 --- a/app/public/components/table/pagination/PaginationModel.js +++ b/app/public/components/table/pagination/PaginationModel.js @@ -14,9 +14,6 @@ import { Observable } from '/js/src/index.js'; const DEFAULT_CURRENT_PAGE = 1; const DEFAULT_ITEMS_COUNT = 0; -const ENABLE_INFINITE_MODE_BY_DEFAULT = false; - -const INFINITE_SCROLL_CHUNK_SIZE = 19; /** * Model to handle pagination @@ -33,7 +30,6 @@ export class PaginationModel extends Observable { this._itemsPerPage = userPreferences.rowsOnSite; this._currentPage = DEFAULT_CURRENT_PAGE; this._itemsCount = DEFAULT_ITEMS_COUNT; - this._isInfiniteScrollEnabled = ENABLE_INFINITE_MODE_BY_DEFAULT; this._userPreferences.observe(() => { this._itemsPerPage = this._userPreferences.rowsOnSite; @@ -50,7 +46,6 @@ export class PaginationModel extends Observable { this._itemsPerPage = this._userPreferences.rowsOnSite; this._currentPage = DEFAULT_CURRENT_PAGE; this._itemsCount = DEFAULT_ITEMS_COUNT; - this._isInfiniteScrollEnabled = ENABLE_INFINITE_MODE_BY_DEFAULT; } /** @@ -116,9 +111,7 @@ export class PaginationModel extends Observable { * @return {number} the amount of items per page */ get itemsPerPage() { - return this.isInfiniteScrollEnabled - ? INFINITE_SCROLL_CHUNK_SIZE - : this._itemsPerPage; + return this._itemsPerPage; } /** @@ -127,12 +120,11 @@ export class PaginationModel extends Observable { * @param {number} amount the amount of items */ set itemsPerPage(amount) { - if (this._isInfiniteScrollEnabled || this._itemsPerPage !== amount) { + if (this._itemsPerPage !== amount) { this._itemsPerPage = amount; this._currentPage = DEFAULT_CURRENT_PAGE; } this._isAmountDropdownVisible = false; - this._isInfiniteScrollEnabled = false; this.notify(); } @@ -172,25 +164,4 @@ export class PaginationModel extends Observable { set itemsCount(itemsCount) { this._itemsCount = itemsCount; } - - /** - * States if the infinite scroll mode is enabled - * - * @return {boolean} true if infinite scroll mode is enabled - */ - get isInfiniteScrollEnabled() { - return this._isInfiniteScrollEnabled; - } - - /** - * Enable the infinite mode - * - * @return {void} - */ - enableInfiniteMode() { - this._isInfiniteScrollEnabled = true; - this._isAmountDropdownVisible = false; - this._currentPage = DEFAULT_CURRENT_PAGE; - this.notify(); - } } diff --git a/app/public/views/periods/PeriodsModel.js b/app/public/views/periods/PeriodsModel.js index e852f374f..658e69f0e 100644 --- a/app/public/views/periods/PeriodsModel.js +++ b/app/public/views/periods/PeriodsModel.js @@ -67,18 +67,8 @@ export default class PeriodsModel extends Observable { /** * @type {Period[]} */ - - /* - * When fetching data, to avoid concurrency issues, save a flag stating if the fetched data should be concatenated with the current one - * (infinite scroll) or if they should replace them - */ - - const shouldKeepExisting = this._pagination.currentPage > 1 && this._pagination.isInfiniteScrollEnabled; - - if (!this._pagination.isInfiniteScrollEnabled) { - this._currentPagePeriods = RemoteData.loading(); - this.notify(); - } + this._currentPagePeriods = RemoteData.loading(); + this.notify(); const params = { 'page[offset]': this._pagination.firstItemOffset, @@ -90,8 +80,7 @@ export default class PeriodsModel extends Observable { const endpoint = `/api/periods?${new URLSearchParams(params).toString()}`; try { const { items, totalCount } = await getRemoteDataSlice(endpoint); - const concatenateWith = shouldKeepExisting ? this._periods.payload || [] : []; - this._currentPagePeriods = RemoteData.success([...concatenateWith, ...items]); + this._currentPagePeriods = RemoteData.success([...items]); this._pagination.itemsCount = totalCount; } catch (errors) { this._currentPagePeriods = RemoteData.failure(errors); From 46e3168ab828d054edd0cf4866c423129e90fb08 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 16 Aug 2023 15:19:45 +0200 Subject: [PATCH 31/82] Fix pagination --- app/public/Model.js | 2 +- .../table/pagination/PaginationModel.js | 19 ++++++++++ app/public/views/periods/PeriodsModel.js | 36 +++++++++++++++++-- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/app/public/Model.js b/app/public/Model.js index 7b73d1506..906c329de 100644 --- a/app/public/Model.js +++ b/app/public/Model.js @@ -68,7 +68,7 @@ export default class Model extends Observable { async handleLocationChange() { switch (this.router.params.page) { case pageNames.periods: - await this.periods.fetchAllPeriods(); + await this.periods.fetchCurrentPagePeriods(); break; default: break; diff --git a/app/public/components/table/pagination/PaginationModel.js b/app/public/components/table/pagination/PaginationModel.js index e90b08848..d8a176747 100644 --- a/app/public/components/table/pagination/PaginationModel.js +++ b/app/public/components/table/pagination/PaginationModel.js @@ -29,6 +29,7 @@ export class PaginationModel extends Observable { this._userPreferences = userPreferences; this._itemsPerPage = userPreferences.rowsOnSite; this._currentPage = DEFAULT_CURRENT_PAGE; + this._currentPageItemsCount = DEFAULT_ITEMS_COUNT; this._itemsCount = DEFAULT_ITEMS_COUNT; this._userPreferences.observe(() => { @@ -164,4 +165,22 @@ export class PaginationModel extends Observable { set itemsCount(itemsCount) { this._itemsCount = itemsCount; } + + /** + * Returns the total amount of items paginated + * + * @return {number} the amount of items + */ + get currentPageItemsCount() { + return this._currentPageItemsCount; + } + + /** + * Define the total amount of items paginated + * + * @param {number} itemsCount the amount of items + */ + set currentPageItemsCount(itemsCount) { + this._currentPageItemsCount = itemsCount; + } } diff --git a/app/public/views/periods/PeriodsModel.js b/app/public/views/periods/PeriodsModel.js index 658e69f0e..8d5ec4c1e 100644 --- a/app/public/views/periods/PeriodsModel.js +++ b/app/public/views/periods/PeriodsModel.js @@ -33,7 +33,8 @@ export default class PeriodsModel extends Observable { this._pagination = new PaginationModel(model.userPreferences); this._pagination.observe(() => { - this.fetchAllPeriods(); this.notify(); + this.fetchCurrentPagePeriods(); + this.notify(); }); this._fields = Object.keys(RCT.fieldNames.periods).map((field) => ({ name: field, visible: true })); @@ -67,6 +68,37 @@ export default class PeriodsModel extends Observable { /** * @type {Period[]} */ + this._allPeriods = RemoteData.loading(); + this.notify(); + + this._allPeriods = RemoteData.notAsked(); + + const endpoint = '/api/periods'; + try { + const { items, totalCount } = await getRemoteDataSlice(endpoint); + this._allPeriods = RemoteData.success([...items]); + this._pagination.itemsCount = totalCount; + } catch (errors) { + this._allPeriods = RemoteData.failure(errors); + } + + this.notify(); + } + + /** + * Fetch all the relevant periods from the API + * + * @return {Promise} void + */ + async fetchCurrentPagePeriods() { + /** + * @type {Period[]} + */ + + if (this._allPeriods.kind === 'NotAsked') { + await this.fetchAllPeriods(); + } + this._currentPagePeriods = RemoteData.loading(); this.notify(); @@ -81,7 +113,7 @@ export default class PeriodsModel extends Observable { try { const { items, totalCount } = await getRemoteDataSlice(endpoint); this._currentPagePeriods = RemoteData.success([...items]); - this._pagination.itemsCount = totalCount; + this._pagination.currentPageItemsCount = totalCount; } catch (errors) { this._currentPagePeriods = RemoteData.failure(errors); } From 628e3f0812ac07cce44e83e3dbb9528ac6244037 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 16 Aug 2023 15:28:28 +0200 Subject: [PATCH 32/82] Cleanup :broom: --- .../table/pagination/PaginationModel.js | 35 ------------------- app/public/views/periods/PeriodsModel.js | 19 ---------- 2 files changed, 54 deletions(-) diff --git a/app/public/components/table/pagination/PaginationModel.js b/app/public/components/table/pagination/PaginationModel.js index d8a176747..420a8b3cb 100644 --- a/app/public/components/table/pagination/PaginationModel.js +++ b/app/public/components/table/pagination/PaginationModel.js @@ -38,17 +38,6 @@ export class PaginationModel extends Observable { }); } - /** - * Reset the pagination to its default values - * - * @return {void} - */ - reset() { - this._itemsPerPage = this._userPreferences.rowsOnSite; - this._currentPage = DEFAULT_CURRENT_PAGE; - this._itemsCount = DEFAULT_ITEMS_COUNT; - } - /** * Defines the current page without notifying observers * @@ -63,29 +52,6 @@ export class PaginationModel extends Observable { return false; } - /** - * If the current page is not the last one, navigate to the next one - * - * @return {void} - */ - goToNextPage() { - if (this._currentPage < this.pagesCount) { - this.currentPage = this.currentPage + 1; - } - } - - /** - * Navigate to the target page - * - * @param {number} page target page - * @return {void} - */ - goToPage(page) { - if (page < this.pagesCount) { - this.currentPage = page; - } - } - /** * Returns the current page * @@ -125,7 +91,6 @@ export class PaginationModel extends Observable { this._itemsPerPage = amount; this._currentPage = DEFAULT_CURRENT_PAGE; } - this._isAmountDropdownVisible = false; this.notify(); } diff --git a/app/public/views/periods/PeriodsModel.js b/app/public/views/periods/PeriodsModel.js index 8d5ec4c1e..4343c0edf 100644 --- a/app/public/views/periods/PeriodsModel.js +++ b/app/public/views/periods/PeriodsModel.js @@ -48,17 +48,6 @@ export default class PeriodsModel extends Observable { this._periods = RemoteData.NotAsked(); } - /** - * Reset this model to its default - * - * @returns {void} - */ - reset() { - this._periods = RemoteData.NotAsked(); - this._fields = Object.keys(RCT.fieldNames.periods).map((field) => ({ fieldName: field, marked: true })); - this._pagination.reset(); - } - /** * Fetch all the relevant periods from the API * @@ -121,14 +110,6 @@ export default class PeriodsModel extends Observable { this.notify(); } - /** - * Get all the periods - * @return {RemoteData} periods - */ - get periods() { - return this._periods; - } - /** * Get current page periods * @return {RemoteData} periods in the current page From 7beb922ba2faab5ea46e5fd58388bd4fd92e7630 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 16 Aug 2023 15:32:04 +0200 Subject: [PATCH 33/82] ItemsCounter cleanup :broom: --- app/public/components/table/itemsCounter.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/public/components/table/itemsCounter.js b/app/public/components/table/itemsCounter.js index 1958f2171..2f204f7cd 100644 --- a/app/public/components/table/itemsCounter.js +++ b/app/public/components/table/itemsCounter.js @@ -13,12 +13,12 @@ */ export default function itemsCounter(paginationModel) { - const currentSite = paginationModel.currentPage; + const { currentPage, itemsPerPage, itemsCount } = paginationModel; - const firstRowIdx = (currentSite - 1) * paginationModel.itemsPerPage + 1; - const lastRowIdx = currentSite * paginationModel.itemsPerPage > paginationModel.itemsCount - ? paginationModel.itemsCount - : currentSite * paginationModel.itemsPerPage; + const firstItemIndex = (currentPage - 1) * itemsPerPage + 1; + const lastItemIndex = currentPage * itemsPerPage > itemsCount + ? itemsCount + : currentPage * itemsPerPage; - return `${firstRowIdx}-${lastRowIdx} of ${paginationModel.itemsCount}`; + return `${firstItemIndex}-${lastItemIndex} of ${itemsCount}`; } From ad51576d8ea974b2469e67595ab2735d66981c87 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 16 Aug 2023 15:37:30 +0200 Subject: [PATCH 34/82] SetRowsOnSite cleanup :broom: --- app/public/components/common/quantityInput.js | 3 +-- app/public/model/UserPreferences.js | 7 ------- .../views/userView/data/pageSettings/pageSettings.js | 3 +-- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/app/public/components/common/quantityInput.js b/app/public/components/common/quantityInput.js index fb4b03cf1..331bc8d74 100644 --- a/app/public/components/common/quantityInput.js +++ b/app/public/components/common/quantityInput.js @@ -14,7 +14,7 @@ import { h } from '/js/src/index.js'; -export default function quantityInput(id, defaultQuantity, callback) { +export default function quantityInput(id, defaultQuantity) { function handleInput(e) { e.preventDefault(); if (e.key === '1' @@ -53,7 +53,6 @@ export default function quantityInput(id, defaultQuantity, callback) { quantity += change; quantity = Math.max(quantity, 1); document.getElementById(id).value = quantity; - () => callback(quantity); }; const increaseButton = h('button.btn.btn-secondary', { diff --git a/app/public/model/UserPreferences.js b/app/public/model/UserPreferences.js index bf54c9ad6..5b35462ad 100644 --- a/app/public/model/UserPreferences.js +++ b/app/public/model/UserPreferences.js @@ -39,13 +39,6 @@ export default class UserPreferences extends Observable { this.detectorList = RCT.detectors.reduce((acc, detector) => ({ ...acc, [detector]: true }), {}); } - setRowsOnSite(rowsOnSite) { - this.rowsOnSite = rowsOnSite; - const url = this.parent.router.getUrl(); - const newUrl = replaceUrlParams(url, { [dataReqParams.rowsOnSite]: this.rowsOnSite }); - this.parent.router.go(newUrl); - } // Obsolete - setItemsPerPage(itemsPerPage) { this.rowsOnSite = itemsPerPage; this.notify(); diff --git a/app/public/views/userView/data/pageSettings/pageSettings.js b/app/public/views/userView/data/pageSettings/pageSettings.js index 864a6981a..da46236db 100644 --- a/app/public/views/userView/data/pageSettings/pageSettings.js +++ b/app/public/views/userView/data/pageSettings/pageSettings.js @@ -99,8 +99,7 @@ export default function pageSettings(userPreferences, close) { h('.flex-wrap.justify-between.items-center', h('.text-dark-blue', 'Rows on site'), quantityInput(rowsOnSiteInputId, - userPreferences.rowsOnSite, - userPreferences.setRowsOnSite)), + userPreferences.rowsOnSite)), h('.flex-wrap.justify-between.items-center', h('.text-dark-blue', 'UI theme'), From ab7af4e690d7c8bbd6791d8c59d98eeb1efa866f Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 16 Aug 2023 16:04:03 +0200 Subject: [PATCH 35/82] Cleanup :broom: --- .../components/messagePanel/messages.js | 25 +++++++++++++------ .../table/pagination/PaginationModel.js | 6 ++--- app/public/components/table/tableManager.js | 24 +++++++++--------- app/public/views/dataAccessPanel.js | 2 +- app/public/views/periods/PeriodsModel.js | 17 +++++++++++++ .../views/periods/overview/periodsContent.js | 3 ++- .../views/periods/overview/periodsPanel.js | 13 ++++------ app/public/views/userView/data/dataPanel.js | 11 ++++---- 8 files changed, 64 insertions(+), 37 deletions(-) diff --git a/app/public/components/messagePanel/messages.js b/app/public/components/messagePanel/messages.js index 1f4d4e4dd..7391fcb27 100644 --- a/app/public/components/messagePanel/messages.js +++ b/app/public/components/messagePanel/messages.js @@ -19,10 +19,14 @@ import spinner from '../common/spinner.js'; const goBack = 'Go back'; const nothingFound = 'Nothing found'; -const requestButton = (model) => h('button.btn.btn-primary.m3', { +const obsoleteRequestButton = (model) => h('button.btn.btn-primary.m3', { onclick: () => model.fetchedData.reqForData(true), }, 'Reload'); +const requestButton = (dataModel) => h('button.btn.btn-primary.m3', { + onclick: () => dataModel.fetchCurrentPageData(), +}, 'Reload'); + const removeCurrentDataButton = (model, label) => h('button.btn.btn-primary.m3', { onclick: () => model.removeCurrentData(), }, label); @@ -31,24 +35,31 @@ export const failureWithStatus = (model, status) => messagePanel( 'no-network-90', 'Failed to load data', `The services are unavailable (status: ${status ? status : 'unknown'})`, - requestButton(model), + obsoleteRequestButton(model), ); -export const failureWithMessage = (model, errorObject) => { +export const failureWithMessage = (dataModel, errorObject) => { const { detail, title } = errorObject.find((e) => Boolean(e)); return messagePanel( 'no-network-90', detail, title, - requestButton(model), + requestButton(dataModel), ); }; -export const unknown = (model) => messagePanel( +export const obsoleteUnknown = (model) => messagePanel( + 'unexpected-90', + 'Unknown error', + 'Request could not be handled properly', + obsoleteRequestButton(model), +); + +export const unknown = (dataModel) => messagePanel( 'unexpected-90', 'Unknown error', 'Request could not be handled properly', - requestButton(model), + requestButton(dataModel), ); export const sessionError = (model) => messagePanel( @@ -88,7 +99,7 @@ export const noDataFound = (model) => messagePanel( 'There is no data to be displayed here', [ removeCurrentDataButton(model, goBack), - requestButton(model), + obsoleteRequestButton(model), ], ); diff --git a/app/public/components/table/pagination/PaginationModel.js b/app/public/components/table/pagination/PaginationModel.js index 420a8b3cb..77704b29e 100644 --- a/app/public/components/table/pagination/PaginationModel.js +++ b/app/public/components/table/pagination/PaginationModel.js @@ -62,7 +62,7 @@ export class PaginationModel extends Observable { } /** - * Defines the current page and notify the change to observers + * Defines the current page and notifies the observers * * @param {number} page the current page */ @@ -123,7 +123,7 @@ export class PaginationModel extends Observable { } /** - * Define the total amount of items paginated + * Defines the total amount of items paginated * * @param {number} itemsCount the amount of items */ @@ -141,7 +141,7 @@ export class PaginationModel extends Observable { } /** - * Define the total amount of items paginated + * Defines the total amount of items paginated * * @param {number} itemsCount the amount of items */ diff --git a/app/public/components/table/tableManager.js b/app/public/components/table/tableManager.js index dd851443f..5d4322198 100644 --- a/app/public/components/table/tableManager.js +++ b/app/public/components/table/tableManager.js @@ -21,7 +21,7 @@ const columnDisplayOptions = { all: 'all', }; -export default function tableManager(periodsModel, model) { +export default function tableManager(dataModel, model) { const columnDisplayOptionsSelectId = 'columns-option-select-id'; function handleColumnOptionDisplayChange() { @@ -29,13 +29,13 @@ export default function tableManager(periodsModel, model) { const selectedOption = columnOptionsSelect.options[columnOptionsSelect.selectedIndex].value; switch (selectedOption) { case columnDisplayOptions.nonEmpty: - periodsModel.fields.forEach((field) => { - periodsModel.toggleFieldVisibility(field, periodsModel.currentPagePeriods.payload.some((p) => p[field.name])); + dataModel.fields.forEach((field) => { + dataModel.toggleFieldVisibility(field, dataModel.currentPageData.payload.some((p) => p[field.name])); }); break; case columnDisplayOptions.all: - for (const field of periodsModel.fields) { - periodsModel.toggleFieldVisibility(field, true); + for (const field of dataModel.fields) { + dataModel.toggleFieldVisibility(field, true); } model.notify(); break; @@ -47,12 +47,12 @@ export default function tableManager(periodsModel, model) { return h('.flex-row.pager-panel.items-center', [ h('.flex-wrap.justify-between.items-center', h('.flex-wrap.justify-between.items-center.ph3', - h('.italic', itemsCounter(periodsModel.pagination))), + h('.italic', itemsCounter(dataModel.pagination))), h('button.btn.icon-only-button.m-right-15', { - className: periodsModel.sortingRowVisible ? 'btn-primary' : 'btn-secondary', - onclick: () => periodsModel.toggleSortingRowVisibility(), - }, periodsModel.sortingRowVisible ? h('.sorting-20-off-white.abs-center') : h('.sorting-20-primary.abs-center')), + className: dataModel.sortingRowVisible ? 'btn-primary' : 'btn-secondary', + onclick: () => dataModel.toggleSortingRowVisibility(), + }, dataModel.sortingRowVisible ? h('.sorting-20-off-white.abs-center') : h('.sorting-20-primary.abs-center')), h('select.select.column-display-options-select', { id: columnDisplayOptionsSelectId, @@ -66,10 +66,10 @@ export default function tableManager(periodsModel, model) { ], iconChevronBottom())), pageSelector( - periodsModel.pagination.currentPage, - periodsModel.pagination.pagesCount, + dataModel.pagination.currentPage, + dataModel.pagination.pagesCount, (page) => { - periodsModel.pagination.currentPage = page; + dataModel.pagination.currentPage = page; }, ), diff --git a/app/public/views/dataAccessPanel.js b/app/public/views/dataAccessPanel.js index 519b5df7e..88551db46 100644 --- a/app/public/views/dataAccessPanel.js +++ b/app/public/views/dataAccessPanel.js @@ -25,7 +25,7 @@ export default function dataAccessPanel(model) { h('section.flex-grow.relative.user-panel-main-content', [ h('.scroll-y.absolute-fill', { id: 'user-panel-main-content' }, - dataPanel(dataAccess, runs, detectors, flags, model)), + dataPanel(model, runs, detectors, flags)), ]), ]), ]); diff --git a/app/public/views/periods/PeriodsModel.js b/app/public/views/periods/PeriodsModel.js index 4343c0edf..b516bce69 100644 --- a/app/public/views/periods/PeriodsModel.js +++ b/app/public/views/periods/PeriodsModel.js @@ -110,6 +110,15 @@ export default class PeriodsModel extends Observable { this.notify(); } + /** + * Fetch all the relevant data from the API + * + * @return {Promise} void + */ + async fetchCurrentPageData() { + await this.fetchCurrentPagePeriods(); + } + /** * Get current page periods * @return {RemoteData} periods in the current page @@ -118,6 +127,14 @@ export default class PeriodsModel extends Observable { return this._currentPagePeriods; } + /** + * Get current page data + * @return {RemoteData} periods in the current page + */ + get currentPageData() { + return this._currentPagePeriods; + } + get visibleFields() { return this._fields.filter((field) => field.visible); } diff --git a/app/public/views/periods/overview/periodsContent.js b/app/public/views/periods/overview/periodsContent.js index e7a911438..da1e0e968 100644 --- a/app/public/views/periods/overview/periodsContent.js +++ b/app/public/views/periods/overview/periodsContent.js @@ -39,7 +39,8 @@ const applicableDataActions = { [dataActions.showFilteringPanel]: true, }; -export default function periodsContent(periodsModel, periods, model) { +export default function periodsContent(periodsModel, model) { + const periods = periodsModel.currentPagePeriods.payload; const { dataAccess } = model; const url = model.router.getUrl(); diff --git a/app/public/views/periods/overview/periodsPanel.js b/app/public/views/periods/overview/periodsPanel.js index e6bbc725d..272988060 100644 --- a/app/public/views/periods/overview/periodsPanel.js +++ b/app/public/views/periods/overview/periodsPanel.js @@ -15,14 +15,11 @@ import periodsContent from './periodsContent.js'; import { waiting, unknown, failureWithMessage } from '../../../components/messagePanel/messages.js'; -export default function periodsPanel(model) { - const { currentPagePeriods } = model.periods; - const { dataAccess } = model; - - return currentPagePeriods.match({ - NotAsked: () => unknown(dataAccess), +export default function periodsPanel(periodsModel, model) { + return periodsModel.currentPagePeriods.match({ + NotAsked: () => unknown(periodsModel, model), Loading: () => waiting(), - Success: () => periodsContent(model.periods, currentPagePeriods.payload, model), - Failure: (errors) => failureWithMessage(model, errors), + Success: () => periodsContent(periodsModel, model), + Failure: (errors) => failureWithMessage(periodsModel, errors), }); } diff --git a/app/public/views/userView/data/dataPanel.js b/app/public/views/userView/data/dataPanel.js index 11c578f79..d5c5505a9 100644 --- a/app/public/views/userView/data/dataPanel.js +++ b/app/public/views/userView/data/dataPanel.js @@ -16,7 +16,7 @@ import tablePanel from './table/tablePanel.js'; import flagsPanel from '../../flags/overview/flagsPanel.js'; import { default as runsPerDataPassPanel } from '../../runs/runsPerDataPass/overview/panel.js'; import { default as runsPerPeriodPanel } from '../../runs/runsPerPeriod/overview/panel.js'; -import { failureWithStatus, unknown, waiting } from '../../../components/messagePanel/messages.js'; +import { failureWithStatus, obsoleteUnknown, waiting } from '../../../components/messagePanel/messages.js'; import { RCT } from '../../../config.js'; import periodsPanel from '../../periods/overview/periodsPanel.js'; const { pageNames } = RCT; @@ -27,17 +27,18 @@ const { pageNames } = RCT; * @returns {*} */ -export default function dataPanel(dataAccess, runs, detectors, flags, model) { +export default function dataPanel(model, runs, detectors, flags) { + const { dataAccess, periods } = model; const { page, index } = dataAccess.getCurrentDataPointer(); const data = dataAccess.fetchedData[page][index]; return data ? data.match({ - NotAsked: () => unknown(dataAccess), + NotAsked: () => obsoleteUnknown(dataAccess), Loading: () => waiting(), Success: () => { switch (page) { case pageNames.periods: - return periodsPanel(model); + return periodsPanel(periods, model); case pageNames.flags: return flagsPanel(dataAccess, runs, detectors, flags); case pageNames.runsPerDataPass: @@ -49,5 +50,5 @@ export default function dataPanel(dataAccess, runs, detectors, flags, model) { } }, Failure: (status) => failureWithStatus(dataAccess, status), - }) : unknown(dataAccess); + }) : obsoleteUnknown(dataAccess); } From 5cf1469f0fb240cbfb38b8bd36f90abe7aa20ce4 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 16 Aug 2023 16:07:12 +0200 Subject: [PATCH 36/82] Cleanup :broom: --- app/public/components/table/tableManager.js | 4 ++-- app/public/views/periods/overview/periodsContent.js | 4 ++-- app/public/views/periods/overview/periodsPanel.js | 2 +- app/public/views/periods/table/periodsTableHeader.js | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/public/components/table/tableManager.js b/app/public/components/table/tableManager.js index 5d4322198..3201a6d47 100644 --- a/app/public/components/table/tableManager.js +++ b/app/public/components/table/tableManager.js @@ -21,7 +21,7 @@ const columnDisplayOptions = { all: 'all', }; -export default function tableManager(dataModel, model) { +export default function tableManager(dataModel) { const columnDisplayOptionsSelectId = 'columns-option-select-id'; function handleColumnOptionDisplayChange() { @@ -36,8 +36,8 @@ export default function tableManager(dataModel, model) { case columnDisplayOptions.all: for (const field of dataModel.fields) { dataModel.toggleFieldVisibility(field, true); + dataModel.notify(); } - model.notify(); break; default: break; diff --git a/app/public/views/periods/overview/periodsContent.js b/app/public/views/periods/overview/periodsContent.js index da1e0e968..aa84ce9f5 100644 --- a/app/public/views/periods/overview/periodsContent.js +++ b/app/public/views/periods/overview/periodsContent.js @@ -58,11 +58,11 @@ export default function periodsContent(periodsModel, model) { ? periodsModel.visibleFields.length > 0 ? h('.p-top-05em', h('.x-scrollable-table.border-sh', - tableManager(periodsModel, model), + tableManager(periodsModel), h(`table.${pageName}-table`, { id: `data-table-${pageName}`, }, - periodsTableHeader(pageName, periodsModel.visibleFields, periods, model), + periodsTableHeader(pageName, periodsModel.visibleFields, periods, dataAccess), h('tbody', { id: `table-body-${pageName}` }, periods.map((period) => periodsTableRow( period, periodsModel.visibleFields, dataAccess, periodsModel, diff --git a/app/public/views/periods/overview/periodsPanel.js b/app/public/views/periods/overview/periodsPanel.js index 272988060..ce501d2ff 100644 --- a/app/public/views/periods/overview/periodsPanel.js +++ b/app/public/views/periods/overview/periodsPanel.js @@ -17,7 +17,7 @@ import { waiting, unknown, failureWithMessage } from '../../../components/messag export default function periodsPanel(periodsModel, model) { return periodsModel.currentPagePeriods.match({ - NotAsked: () => unknown(periodsModel, model), + NotAsked: () => unknown(periodsModel), Loading: () => waiting(), Success: () => periodsContent(periodsModel, model), Failure: (errors) => failureWithMessage(periodsModel, errors), diff --git a/app/public/views/periods/table/periodsTableHeader.js b/app/public/views/periods/table/periodsTableHeader.js index c837be2c1..070ab0f92 100644 --- a/app/public/views/periods/table/periodsTableHeader.js +++ b/app/public/views/periods/table/periodsTableHeader.js @@ -15,7 +15,7 @@ import { h } from '/js/src/index.js'; import { getHeaderSpecial, headerSpecPresent, nonDisplayable } from '../../userView/data/headersSpecials.js'; -export default function periodsTableHeader(pageName, visibleFields, data, model) { +export default function periodsTableHeader(pageName, visibleFields, data, dataAccessModel) { const columnsHeadersArray = (visibleFields, model) => { const dataHeaders = visibleFields.map((field) => h(`th.${pageName}-${field.name}-header`, { @@ -43,5 +43,5 @@ export default function periodsTableHeader(pageName, visibleFields, data, model) }))); return h('thead.header', - h('tr', [rowsOptions(model.dataAccess, data)].concat(columnsHeadersArray(visibleFields, model.dataAccess)))); + h('tr', [rowsOptions(dataAccessModel, data)].concat(columnsHeadersArray(visibleFields, dataAccessModel)))); } From 21acc3dc917278b6ac26cf29335deb9d4c0d1485 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 16 Aug 2023 16:45:20 +0200 Subject: [PATCH 37/82] Remove unused delay function --- app/lib/utils/obj-utils.js | 5 ----- test/lib/utils/utils.test.js | 10 ---------- 2 files changed, 15 deletions(-) diff --git a/app/lib/utils/obj-utils.js b/app/lib/utils/obj-utils.js index 6d973b18e..a00f80568 100644 --- a/app/lib/utils/obj-utils.js +++ b/app/lib/utils/obj-utils.js @@ -67,10 +67,6 @@ function switchCase(caseName, cases, opts) { opts.default ? opts.default : (() => { throw new Error('not last found option, no case, no default case'); })(); } -function delay(time) { - return new Promise((resolve) => setTimeout(resolve, time)); -} - function replaceAll(s, pattern, replace) { const p = s.split(pattern); return p.join(replace); @@ -122,7 +118,6 @@ module.exports = { reversePrimitiveObject, filterObject, switchCase, - delay, replaceAll, arrayToChunks, applyOptsToObj, diff --git a/test/lib/utils/utils.test.js b/test/lib/utils/utils.test.js index ba6f71ab6..db652211c 100644 --- a/test/lib/utils/utils.test.js +++ b/test/lib/utils/utils.test.js @@ -85,16 +85,6 @@ module.exports = () => { }); }); - describe('Delay', () => { - it('Waits requested number of miliseconds', async () => { - const delayTime = 100; - const start = Date.now(); - await Utils.delay(delayTime); - const end = Date.now(); - assert(start + delayTime <= end); - }); - }); - describe('Array to chunks', () => { it('Should split an array into chunks', async () => { const array = [1, 2, 3, 4, 5]; From 96b1cef41b7ecfed9113d28e9d538ff3a0d1d1ca Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 16 Aug 2023 17:59:59 +0200 Subject: [PATCH 38/82] Rename header columns --- app/public/views/periods/table/periodsTableHeader.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/public/views/periods/table/periodsTableHeader.js b/app/public/views/periods/table/periodsTableHeader.js index 070ab0f92..2e283edac 100644 --- a/app/public/views/periods/table/periodsTableHeader.js +++ b/app/public/views/periods/table/periodsTableHeader.js @@ -16,7 +16,7 @@ import { h } from '/js/src/index.js'; import { getHeaderSpecial, headerSpecPresent, nonDisplayable } from '../../userView/data/headersSpecials.js'; export default function periodsTableHeader(pageName, visibleFields, data, dataAccessModel) { - const columnsHeadersArray = (visibleFields, model) => { + const headerColumns = (visibleFields, model) => { const dataHeaders = visibleFields.map((field) => h(`th.${pageName}-${field.name}-header`, { scope: 'col', @@ -28,7 +28,7 @@ export default function periodsTableHeader(pageName, visibleFields, data, dataAc return dataHeaders; }; - const rowsOptions = (model, data) => + const headerCheckbox = (model, data) => h('th', { scope: 'col' }, h('.relative', h(`input.checkbox.abs-center${data.every((r) => r.selected) ? '.ticked' : ''}`, { @@ -43,5 +43,7 @@ export default function periodsTableHeader(pageName, visibleFields, data, dataAc }))); return h('thead.header', - h('tr', [rowsOptions(dataAccessModel, data)].concat(columnsHeadersArray(visibleFields, dataAccessModel)))); + h('tr', + headerCheckbox(dataAccessModel, data), + headerColumns(visibleFields, dataAccessModel))); } From 7745401146ff3e5894e216bfe42977fe94ea27e9 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 16 Aug 2023 18:07:00 +0200 Subject: [PATCH 39/82] Cleanup :broom: --- app/public/views/periods/overview/periodsContent.js | 2 +- app/public/views/periods/table/periodsTableHeader.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/public/views/periods/overview/periodsContent.js b/app/public/views/periods/overview/periodsContent.js index aa84ce9f5..5660d5e8e 100644 --- a/app/public/views/periods/overview/periodsContent.js +++ b/app/public/views/periods/overview/periodsContent.js @@ -62,7 +62,7 @@ export default function periodsContent(periodsModel, model) { h(`table.${pageName}-table`, { id: `data-table-${pageName}`, }, - periodsTableHeader(pageName, periodsModel.visibleFields, periods, dataAccess), + periodsTableHeader(periodsModel, pageName, periodsModel.visibleFields, periods, dataAccess), h('tbody', { id: `table-body-${pageName}` }, periods.map((period) => periodsTableRow( period, periodsModel.visibleFields, dataAccess, periodsModel, diff --git a/app/public/views/periods/table/periodsTableHeader.js b/app/public/views/periods/table/periodsTableHeader.js index 2e283edac..740d109fb 100644 --- a/app/public/views/periods/table/periodsTableHeader.js +++ b/app/public/views/periods/table/periodsTableHeader.js @@ -15,7 +15,7 @@ import { h } from '/js/src/index.js'; import { getHeaderSpecial, headerSpecPresent, nonDisplayable } from '../../userView/data/headersSpecials.js'; -export default function periodsTableHeader(pageName, visibleFields, data, dataAccessModel) { +export default function periodsTableHeader(periodsModel, pageName, visibleFields, data, dataAccessModel) { const headerColumns = (visibleFields, model) => { const dataHeaders = visibleFields.map((field) => h(`th.${pageName}-${field.name}-header`, { @@ -44,6 +44,6 @@ export default function periodsTableHeader(pageName, visibleFields, data, dataAc return h('thead.header', h('tr', - headerCheckbox(dataAccessModel, data), - headerColumns(visibleFields, dataAccessModel))); + headerCheckbox(periodsModel, data), + headerColumns(dataAccessModel, visibleFields))); } From 574d88459e578d516d494ea107564d4253d7be55 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 16 Aug 2023 18:12:59 +0200 Subject: [PATCH 40/82] Cleanup :broom: --- app/public/views/runs/runsPerDataPass/overview/content.js | 2 +- app/public/views/userView/data/table/tablePanel.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/public/views/runs/runsPerDataPass/overview/content.js b/app/public/views/runs/runsPerDataPass/overview/content.js index 9420bfcd1..b31989cef 100644 --- a/app/public/views/runs/runsPerDataPass/overview/content.js +++ b/app/public/views/runs/runsPerDataPass/overview/content.js @@ -71,7 +71,7 @@ export default function content(model, runs, detectors) { ? visibleFields.length > 0 ? h('.p-top-05em', h('.x-scrollable-table.border-sh', - obsoletePager(model, data, false), // + obsoletePager(model, data, false), h('table.runs-table', { id: `data-table-${data.url}`, }, diff --git a/app/public/views/userView/data/table/tablePanel.js b/app/public/views/userView/data/table/tablePanel.js index 689f131d8..32bd8d0b9 100644 --- a/app/public/views/userView/data/table/tablePanel.js +++ b/app/public/views/userView/data/table/tablePanel.js @@ -59,7 +59,7 @@ export default function tablePanel(model, runs) { data.rows = data.rows.filter((item) => item.name != 'null'); - const cellsSpecials = pagesCellsSpecials[dataPointer.page]; // + const cellsSpecials = pagesCellsSpecials[dataPointer.page]; const { fields } = data; const visibleFields = fields.filter((f) => f.marked); From 6060f5778323ebd89aa9fcc76ddfe083e02437a0 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Fri, 18 Aug 2023 14:37:29 +0200 Subject: [PATCH 41/82] Quickfix --- app/public/views/periods/table/periodsTableHeader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/public/views/periods/table/periodsTableHeader.js b/app/public/views/periods/table/periodsTableHeader.js index 740d109fb..8b1edb1c8 100644 --- a/app/public/views/periods/table/periodsTableHeader.js +++ b/app/public/views/periods/table/periodsTableHeader.js @@ -16,7 +16,7 @@ import { h } from '/js/src/index.js'; import { getHeaderSpecial, headerSpecPresent, nonDisplayable } from '../../userView/data/headersSpecials.js'; export default function periodsTableHeader(periodsModel, pageName, visibleFields, data, dataAccessModel) { - const headerColumns = (visibleFields, model) => { + const headerColumns = (model, visibleFields) => { const dataHeaders = visibleFields.map((field) => h(`th.${pageName}-${field.name}-header`, { scope: 'col', From 6b67ef7b4b82e6b789fee42abc15e83b4b83959f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fio=C5=82ko=20Palca?= <48785655+Ehevi@users.noreply.github.com> Date: Wed, 23 Aug 2023 15:11:10 +0200 Subject: [PATCH 42/82] [ORCT-157] Mark obsolete data requests as @deprecated (#176) --- .../components/messagePanel/messages.js | 28 +++++++++++++++++++ app/public/components/table/obsoletePager.js | 9 ++++++ app/public/model/DataAccessModel.js | 6 ++++ app/public/model/data/FetchedData.js | 4 +-- app/public/model/data/FetchedDataManager.js | 4 +++ .../data/table/obsoleteItemsCounter.js | 6 ++++ 6 files changed, 55 insertions(+), 2 deletions(-) diff --git a/app/public/components/messagePanel/messages.js b/app/public/components/messagePanel/messages.js index 7391fcb27..e99631a9d 100644 --- a/app/public/components/messagePanel/messages.js +++ b/app/public/components/messagePanel/messages.js @@ -19,6 +19,13 @@ import spinner from '../common/spinner.js'; const goBack = 'Go back'; const nothingFound = 'Nothing found'; +/** + * Uses deprecated data request. + * Please use the `requestButton` with the specific dataModel (e.g. `periodsModel`) instead. + * @deprecated + * @param {DataAccessModel} model dataAccessModel + * @returns {button} button that enables user to request data + */ const obsoleteRequestButton = (model) => h('button.btn.btn-primary.m3', { onclick: () => model.fetchedData.reqForData(true), }, 'Reload'); @@ -31,6 +38,14 @@ const removeCurrentDataButton = (model, label) => h('button.btn.btn-primary.m3', onclick: () => model.removeCurrentData(), }, label); +/** + * Uses deprecated `obsoleteRequestButton`. + * Please use the `failureWithMessage` with the specific dataModel (e.g. `periodsModel`) and errorObject instead. + * @deprecated + * @param {DataAccessModel} model dataAccessModel + * @param {number} status request status + * @returns {messagePanel} messagePanel informing the user about an unknown error. + */ export const failureWithStatus = (model, status) => messagePanel( 'no-network-90', 'Failed to load data', @@ -48,6 +63,13 @@ export const failureWithMessage = (dataModel, errorObject) => { ); }; +/** + * Uses deprecated `obsoleteRequestButton`. + * Please use the `unknown` with the specific dataModel (e.g. `periodsModel`) instead. + * @deprecated + * @param {DataAccessModel} model dataAccessModel + * @returns {messagePanel} messagePanel informing the user about an unknown error. + */ export const obsoleteUnknown = (model) => messagePanel( 'unexpected-90', 'Unknown error', @@ -93,6 +115,12 @@ export const noMatchingData = (model, page) => messagePanel( ], ); +/** + * Uses deprecated `obsoleteRequestButton`. + * @deprecated + * @param {DataAccessModel} model dataAccessModel + * @returns {messagePanel} messagePanel informing the user about an unknown error. + */ export const noDataFound = (model) => messagePanel( 'nothing-found-90', nothingFound, diff --git a/app/public/components/table/obsoletePager.js b/app/public/components/table/obsoletePager.js index c26737ac5..34f94815a 100644 --- a/app/public/components/table/obsoletePager.js +++ b/app/public/components/table/obsoletePager.js @@ -18,6 +18,15 @@ import { RCT } from '../../config.js'; const { site } = RCT.dataReqParams; +/** + * Uses obsolete model. + * @deprecated + * @param {DataAccessModel} model dataAccessModel + * @param {*} data data + * @param {boolean} pagerOnly indicates whether the component should display + * only the pager or sorting row and column display controller as well + * @returns {obsoletePager} row with pager and table display properties controllers + */ export default function obsoletePager(model, data, pagerOnly = true) { const sitesNumber = Math.ceil(data.totalRecordsNumber / data.rowsOnSite); const currentSite = Number(Object.fromEntries(data.url.searchParams.entries())[site]); diff --git a/app/public/model/DataAccessModel.js b/app/public/model/DataAccessModel.js index 17050ee51..358936b2b 100644 --- a/app/public/model/DataAccessModel.js +++ b/app/public/model/DataAccessModel.js @@ -21,6 +21,12 @@ import { RCT } from '../config.js'; const { messageTimeout } = RCT; const { states } = RCT.dataAccess; +/** + * General class for managing the data access + * @deprecated + * Please use separate models for each view (e.g. periodsModel). + * @param {Model} parent main model + */ export default class DataAccessModel extends Observable { constructor(parent) { super(); diff --git a/app/public/model/data/FetchedData.js b/app/public/model/data/FetchedData.js index e8e1b4d5e..a5ff36821 100644 --- a/app/public/model/data/FetchedData.js +++ b/app/public/model/data/FetchedData.js @@ -23,9 +23,9 @@ const DRF = RCT.dataResponseFields; * set of data held in this structure are fully defined by the url given as on of constructor arguments * when some filtering parameters are or site, etc. is changed * the url is also changed in order to be consistent with data - * + * @deprecated + * Please use separate models for each view (e.g. periodsModel). */ - export default class FetchedData { constructor(url, content, userPreferences, totalRecordsNumber = null) { this.url = url; diff --git a/app/public/model/data/FetchedDataManager.js b/app/public/model/data/FetchedDataManager.js index fc5c01e22..c0a78fb4c 100644 --- a/app/public/model/data/FetchedDataManager.js +++ b/app/public/model/data/FetchedDataManager.js @@ -23,6 +23,10 @@ const { dataReqParams, pageNames, dataAccess } = 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 + * @deprecated + * Please use separate models for each view (e.g. periodsModel). + * @param {Router} router parent router + * @param {DataAccessModel} model data access model */ export default class FetchedDataManager { constructor(router, model) { diff --git a/app/public/views/userView/data/table/obsoleteItemsCounter.js b/app/public/views/userView/data/table/obsoleteItemsCounter.js index 3f58b4b6e..6da6609a6 100644 --- a/app/public/views/userView/data/table/obsoleteItemsCounter.js +++ b/app/public/views/userView/data/table/obsoleteItemsCounter.js @@ -16,6 +16,12 @@ import { RCT } from '../../../../config.js'; const siteParamName = RCT.dataReqParams.site; +/** + * Please use the new items counter instead that uses the paginationModel + * @deprecated + * @param {*} data data + * @returns {string} range of items diplayed on the given page and the total number of fetched records + */ export default function obsoleteItemsCounter(data) { const currentSite = Number(Object.fromEntries(data.url.searchParams.entries())[siteParamName]); From e9365017d9a6f361b6bd857324c40685822d3952 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Thu, 24 Aug 2023 13:31:59 +0200 Subject: [PATCH 43/82] Add items counter test --- test/public/components/index.js | 18 +++++++++++ test/public/components/itemsCounter.test.js | 35 +++++++++++++++++++++ test/public/index.js | 2 ++ test/public/utils/csvExport.test.js | 1 - 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 test/public/components/index.js create mode 100644 test/public/components/itemsCounter.test.js diff --git a/test/public/components/index.js b/test/public/components/index.js new file mode 100644 index 000000000..eb993bf21 --- /dev/null +++ b/test/public/components/index.js @@ -0,0 +1,18 @@ +/** + * @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 itemsCounterSuite = require('./itemsCounter.test'); + +module.exports = () => { + describe('Items counter', itemsCounterSuite); +}; diff --git a/test/public/components/itemsCounter.test.js b/test/public/components/itemsCounter.test.js new file mode 100644 index 000000000..d0840bc40 --- /dev/null +++ b/test/public/components/itemsCounter.test.js @@ -0,0 +1,35 @@ +/** + * @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 req = require('esm')(module) +const itemsCounter = req('../../../app/public/components/table/itemsCounter').default; +const assert = require('assert'); + +module.exports = () => { + describe('Items counter', () => { + const mockPaginationModel = { + currentPage: 5, + itemsPerPage: 10, + itemsCount: 57, + } + const expectedResult = '41-50 of 57'; + + it('should not return null', () => { + assert(itemsCounter(mockPaginationModel) !== null); + }); + + it('should count the items as expected', () => { + assert.equal(itemsCounter(mockPaginationModel), expectedResult); + }); + }); +}; diff --git a/test/public/index.js b/test/public/index.js index f732fe025..9f8226155 100644 --- a/test/public/index.js +++ b/test/public/index.js @@ -12,7 +12,9 @@ */ const UtilitiesSuite = require('./utils'); +const ComponentsSuite = require('./components'); module.exports = () => { + describe('Components', ComponentsSuite); describe('Utilities', UtilitiesSuite); }; diff --git a/test/public/utils/csvExport.test.js b/test/public/utils/csvExport.test.js index 9f51f79a1..b7707eee9 100644 --- a/test/public/utils/csvExport.test.js +++ b/test/public/utils/csvExport.test.js @@ -12,7 +12,6 @@ */ const req = require('esm')(module) -const csvExport = req('../../../app/public/utils/csvExport').default; const preparedData = req('../../../app/public/utils/csvExport').preparedData; const preparedFile = req('../../../app/public/utils/csvExport').preparedFile; const assert = require('assert'); From efea3d558bbf102ef89bde7254fd139778a1f7c7 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Fri, 18 Aug 2023 15:12:42 +0200 Subject: [PATCH 44/82] Rename files --- .../components/buttons/dataActionButtons.js | 14 +++- app/public/components/sidebar/sidebar.js | 1 + .../{csvExport.js => obsoleteCsvExport.js} | 2 +- .../views/flags/overview/flagsContent.js | 3 +- app/public/views/modal/modal.js | 10 +++ app/public/views/periods/PeriodsModel.js | 22 +++++++ .../views/periods/overview/periodsContent.js | 1 + .../runs/runsPerDataPass/overview/content.js | 3 +- .../runs/runsPerPeriod/overview/content.js | 3 +- .../views/userView/data/flagsDataPanel.js | 65 ------------------- .../views/userView/data/table/tablePanel.js | 3 +- test/public/utils/index.js | 2 +- ...port.test.js => obsoleteCsvExport.test.js} | 4 +- 13 files changed, 58 insertions(+), 75 deletions(-) rename app/public/utils/{csvExport.js => obsoleteCsvExport.js} (96%) delete mode 100644 app/public/views/userView/data/flagsDataPanel.js rename test/public/utils/{csvExport.test.js => obsoleteCsvExport.test.js} (94%) diff --git a/app/public/components/buttons/dataActionButtons.js b/app/public/components/buttons/dataActionButtons.js index 1dc637b1e..6469eb884 100644 --- a/app/public/components/buttons/dataActionButtons.js +++ b/app/public/components/buttons/dataActionButtons.js @@ -13,12 +13,14 @@ */ import { h, iconDataTransferDownload, iconReload } from '/js/src/index.js'; -import downloadCSV from '../../utils/csvExport.js'; +import obsoleteDownloadCSV from '../../utils/obsoleteCsvExport.js'; import copyLinkButton from './copyLinkButton.js'; +import { modalIds, showModal } from '../../views/modal/modal.js'; export const dataActions = { hide: 'Hide', reload: 'Reload', + obsoleteDownloadCSV: 'Download CSV (obsolete)', downloadCSV: 'Download CSV', copyLink: 'Copy link', showFilteringPanel: 'Filter', @@ -35,10 +37,18 @@ export default function dataActionButtons(model, applicableDataActions) { }, iconReload()) : '', + applicableDataActions[dataActions.obsoleteDownloadCSV] + ? h('button.btn.btn-secondary.icon-only-button', { + onclick: () => { + obsoleteDownloadCSV(model); + }, + }, iconDataTransferDownload()) + : '', + applicableDataActions[dataActions.downloadCSV] ? h('button.btn.btn-secondary.icon-only-button', { onclick: () => { - downloadCSV(model); + showModal(modalIds.dataExport.modal); }, }, iconDataTransferDownload()) : '', diff --git a/app/public/components/sidebar/sidebar.js b/app/public/components/sidebar/sidebar.js index 8b54da48c..3cc097331 100644 --- a/app/public/components/sidebar/sidebar.js +++ b/app/public/components/sidebar/sidebar.js @@ -33,6 +33,7 @@ export default function sidebar(model) { modal(modalIds.about.modal), modal(modalIds.pageSettings.modal, model), modal(modalIds.detectors.modal, model), + modal(modalIds.dataExport.modal, model), h('.logo.ph3.hide-on-close'), h('.flex-column.gap-20', h('.sidebar-section', diff --git a/app/public/utils/csvExport.js b/app/public/utils/obsoleteCsvExport.js similarity index 96% rename from app/public/utils/csvExport.js rename to app/public/utils/obsoleteCsvExport.js index 4f7c331e8..f3e654f6f 100644 --- a/app/public/utils/csvExport.js +++ b/app/public/utils/obsoleteCsvExport.js @@ -39,7 +39,7 @@ export const preparedFile = (model) => { const replacer = (key, value) => value === null ? '' : value; -export default function downloadCSV(model) { +export default function obsoleteDownloadCSV(model) { const file = preparedFile(model); const link = document.createElement('a'); link.setAttribute('href', file.uri); diff --git a/app/public/views/flags/overview/flagsContent.js b/app/public/views/flags/overview/flagsContent.js index 8c8bf2959..977e7f9b7 100644 --- a/app/public/views/flags/overview/flagsContent.js +++ b/app/public/views/flags/overview/flagsContent.js @@ -24,7 +24,8 @@ import dataActionButtons, { dataActions } from '../../../components/buttons/data const applicableDataActions = { [dataActions.hide]: false, [dataActions.reload]: true, - [dataActions.downloadCSV]: true, + [dataActions.obsoleteDownloadCSV]: true, + [dataActions.downloadCSV]: false, [dataActions.copyLink]: true, [dataActions.showFilteringPanel]: false, }; diff --git a/app/public/views/modal/modal.js b/app/public/views/modal/modal.js index cc95018da..ee21b9a4c 100644 --- a/app/public/views/modal/modal.js +++ b/app/public/views/modal/modal.js @@ -35,6 +35,10 @@ export const modalIds = { modal: 'detectorSettingsModalId', content: 'detectorSettingsContentId', }, + dataExport: { + modal: 'dataExportModalId', + content: 'dataExportContentId', + } }; const allModals = () => ({ @@ -83,5 +87,11 @@ export const modal = (modalId, model = null) => { id: modalIds.detectors.content, }, detectorSettings(model.parent.userPreferences))); } + case modalIds.dataExport.modal: { + return h(`.${modalClassNames.modal}`, { id: modalIds.detectors.modal }, + h(`.${modalClassNames.content}.abs-center.p3`, { + id: modalIds.detectors.content, + }, detectorSettings(model.parent.userPreferences))); + } } }; diff --git a/app/public/views/periods/PeriodsModel.js b/app/public/views/periods/PeriodsModel.js index b516bce69..b05624725 100644 --- a/app/public/views/periods/PeriodsModel.js +++ b/app/public/views/periods/PeriodsModel.js @@ -119,6 +119,28 @@ export default class PeriodsModel extends Observable { await this.fetchCurrentPagePeriods(); } + /** + * Create the export with the variables set in the model, handling errors appropriately + * @param {Object} content The source content. + * @param {String} fileName The name of the file including the output format. + * @return {void} + */ + async createPeriodsExport(content, fileName) { + if (content.length > 0) { + this.getSelectedExportType() === 'CSV' + ? createCSVExport(content, `${fileName}.csv`, 'text/csv;charset=utf-8;') + : createJSONExport(content, `${fileName}.json`, 'application/json'); + } else { + this._currentPageRuns = RemoteData.failure([ + { + title: 'No data found', + detail: 'No valid runs were found for provided run number(s)', + }, + ]); + this.notify(); + } + } + /** * Get current page periods * @return {RemoteData} periods in the current page diff --git a/app/public/views/periods/overview/periodsContent.js b/app/public/views/periods/overview/periodsContent.js index 5660d5e8e..11ffcf924 100644 --- a/app/public/views/periods/overview/periodsContent.js +++ b/app/public/views/periods/overview/periodsContent.js @@ -34,6 +34,7 @@ const pageName = RCT.pageNames.periods; const applicableDataActions = { [dataActions.hide]: true, [dataActions.reload]: true, + [dataActions.obsoleteDownloadCSV]: false, [dataActions.downloadCSV]: true, [dataActions.copyLink]: true, [dataActions.showFilteringPanel]: true, diff --git a/app/public/views/runs/runsPerDataPass/overview/content.js b/app/public/views/runs/runsPerDataPass/overview/content.js index b31989cef..20b13953f 100644 --- a/app/public/views/runs/runsPerDataPass/overview/content.js +++ b/app/public/views/runs/runsPerDataPass/overview/content.js @@ -35,7 +35,8 @@ const { pageNames } = RCT; const applicableDataActions = { [dataActions.hide]: true, [dataActions.reload]: true, - [dataActions.downloadCSV]: true, + [dataActions.obsoleteDownloadCSV]: true, + [dataActions.downloadCSV]: false, [dataActions.copyLink]: true, [dataActions.showFilteringPanel]: true, }; diff --git a/app/public/views/runs/runsPerPeriod/overview/content.js b/app/public/views/runs/runsPerPeriod/overview/content.js index eda7c1785..4084cb903 100644 --- a/app/public/views/runs/runsPerPeriod/overview/content.js +++ b/app/public/views/runs/runsPerPeriod/overview/content.js @@ -33,7 +33,8 @@ const { pageNames } = RCT; const applicableDataActions = { [dataActions.hide]: true, [dataActions.reload]: true, - [dataActions.downloadCSV]: true, + [dataActions.obsoleteDownloadCSV]: true, + [dataActions.downloadCSV]: false, [dataActions.copyLink]: true, [dataActions.showFilteringPanel]: true, }; diff --git a/app/public/views/userView/data/flagsDataPanel.js b/app/public/views/userView/data/flagsDataPanel.js deleted file mode 100644 index 5bc2d0b66..000000000 --- a/app/public/views/userView/data/flagsDataPanel.js +++ /dev/null @@ -1,65 +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 { h, iconDataTransferDownload, iconReload } from '/js/src/index.js'; -import filter from './table/filtering/filter.js'; - -import downloadCSV from '../../../../utils/csvExport.js'; - -import flagsVisualization from '../../flags/flagsVisualization.js'; -import flagsTable from '../../flags/flagsTable.js'; -import flagBreadCrumbs from '../../../components/common/flagBreadcrumbs.js'; -import detectorName from './flags/detectorName.js'; -import copyLinkButton from '../../../components/buttons/copyLinkButton.js'; - -export default function flagsDataPanel(model) { - 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 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()), - - copyLinkButton(model.router.getUrl().toString()), - - 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 h('.p-1em', [ - h('.flex-wrap.justify-between.items-center', - h('.flex-wrap.justify-between.items-center', - flagBreadCrumbs(model, dataPassName, run, detectorName(detector))), - - functionalities(model)), - model.searchFieldsVisible ? filter(model) : '', - - flagsVisualization(model, dataPassName, run, detectorName(detector)), - flagsTable(model, run, detectorName(detector)), - ]); -} diff --git a/app/public/views/userView/data/table/tablePanel.js b/app/public/views/userView/data/table/tablePanel.js index 32bd8d0b9..b0cc44bc3 100644 --- a/app/public/views/userView/data/table/tablePanel.js +++ b/app/public/views/userView/data/table/tablePanel.js @@ -35,7 +35,8 @@ const { pageNames } = RCT; const applicableDataActions = { [dataActions.hide]: true, [dataActions.reload]: true, - [dataActions.downloadCSV]: true, + [dataActions.obsoleteDownloadCSV]: true, + [dataActions.downloadCSV]: false, [dataActions.copyLink]: true, [dataActions.showFilteringPanel]: true, }; diff --git a/test/public/utils/index.js b/test/public/utils/index.js index 9d2623504..3a97df3b7 100644 --- a/test/public/utils/index.js +++ b/test/public/utils/index.js @@ -11,7 +11,7 @@ * or submit itself to any jurisdiction. */ -const CSVExportSuite = require('./csvExport.test'); +const CSVExportSuite = require('./obsoleteCsvExport.test'); const dataProcessingSuite = require('./dataProcessing.test'); const filterUtilsSuite = require('./filterUtils.test'); const urlUtilsSuite = require('./urlUtils.test'); diff --git a/test/public/utils/csvExport.test.js b/test/public/utils/obsoleteCsvExport.test.js similarity index 94% rename from test/public/utils/csvExport.test.js rename to test/public/utils/obsoleteCsvExport.test.js index b7707eee9..b4e6ae47d 100644 --- a/test/public/utils/csvExport.test.js +++ b/test/public/utils/obsoleteCsvExport.test.js @@ -12,8 +12,8 @@ */ const req = require('esm')(module) -const preparedData = req('../../../app/public/utils/csvExport').preparedData; -const preparedFile = req('../../../app/public/utils/csvExport').preparedFile; +const preparedData = req('../../../app/public/utils/obsoleteCsvExport').preparedData; +const preparedFile = req('../../../app/public/utils/obsoleteCsvExport').preparedFile; const assert = require('assert'); module.exports = () => { From e3c3103b8b2ec812b9975fcaabc4c5fc68c0fe8f Mon Sep 17 00:00:00 2001 From: Ehevi Date: Fri, 18 Aug 2023 15:43:26 +0200 Subject: [PATCH 45/82] Prepare periods export modal --- app/public/views/modal/modal.js | 11 +- app/public/views/periods/PeriodsModel.js | 22 ---- .../views/periods/overview/periodsExport.js | 115 ++++++++++++++++++ 3 files changed, 122 insertions(+), 26 deletions(-) create mode 100644 app/public/views/periods/overview/periodsExport.js diff --git a/app/public/views/modal/modal.js b/app/public/views/modal/modal.js index ee21b9a4c..076e0b7cb 100644 --- a/app/public/views/modal/modal.js +++ b/app/public/views/modal/modal.js @@ -16,6 +16,7 @@ import { h } from '/js/src/index.js'; import pageSettings from '../userView/data/pageSettings/pageSettings.js'; import about from '../../components/about/about.js'; import detectorSettings from '../userView/data/detectorSettings/detectorSettings.js'; +import periodsExport from '../periods/overview/periodsExport.js'; export const modalClassNames = { modal: 'modal', @@ -38,7 +39,7 @@ export const modalIds = { dataExport: { modal: 'dataExportModalId', content: 'dataExportContentId', - } + }, }; const allModals = () => ({ @@ -88,10 +89,12 @@ export const modal = (modalId, model = null) => { }, detectorSettings(model.parent.userPreferences))); } case modalIds.dataExport.modal: { - return h(`.${modalClassNames.modal}`, { id: modalIds.detectors.modal }, + return h(`.${modalClassNames.modal}`, { id: modalIds.dataExport.modal }, h(`.${modalClassNames.content}.abs-center.p3`, { - id: modalIds.detectors.content, - }, detectorSettings(model.parent.userPreferences))); + id: modalIds.dataExport.content, + }, periodsExport(model.parent.userPreferences, () => { + document.getElementById(modalIds.dataExport.modal).style.display = 'none'; + }))); } } }; diff --git a/app/public/views/periods/PeriodsModel.js b/app/public/views/periods/PeriodsModel.js index b05624725..b516bce69 100644 --- a/app/public/views/periods/PeriodsModel.js +++ b/app/public/views/periods/PeriodsModel.js @@ -119,28 +119,6 @@ export default class PeriodsModel extends Observable { await this.fetchCurrentPagePeriods(); } - /** - * Create the export with the variables set in the model, handling errors appropriately - * @param {Object} content The source content. - * @param {String} fileName The name of the file including the output format. - * @return {void} - */ - async createPeriodsExport(content, fileName) { - if (content.length > 0) { - this.getSelectedExportType() === 'CSV' - ? createCSVExport(content, `${fileName}.csv`, 'text/csv;charset=utf-8;') - : createJSONExport(content, `${fileName}.json`, 'application/json'); - } else { - this._currentPageRuns = RemoteData.failure([ - { - title: 'No data found', - detail: 'No valid runs were found for provided run number(s)', - }, - ]); - this.notify(); - } - } - /** * Get current page periods * @return {RemoteData} periods in the current page diff --git a/app/public/views/periods/overview/periodsExport.js b/app/public/views/periods/overview/periodsExport.js new file mode 100644 index 000000000..2da684492 --- /dev/null +++ b/app/public/views/periods/overview/periodsExport.js @@ -0,0 +1,115 @@ +/** + * @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, iconChevronBottom } from '/js/src/index.js'; + +export default function periodsExport(userPreferences, close) { + const rowsPreferenceSelectId = 'rows-preference-selection'; + const columnsPreferenceSelectId = 'columns-preference-selection'; + const exportPreferences = { + all: 'all', + currentPage: 'currentPage', + selected: 'selected', + notSelected: 'notSelected', + visible: 'visible', + }; + const title = h('h3.text-primary', 'Export'); + + const exportData = () => { + close(); + }; + + const handleRowsPreferenceSelection = () => { + const exportPreferenceSelection = document.getElementById(rowsPreferenceSelectId); + const selectedPreference = exportPreferenceSelection.options[exportPreferenceSelection.selectedIndex].value; + switch (selectedPreference) { + case exportPreferences.all: + /* */ + break; + case exportPreferences.currentPage: + /* */ + break; + case exportPreferences.selected: + /* */ + break; + case exportPreferences.notSelected: + /* */ + break; + case exportPreferences.visible: + /* */ + break; + default: + break; + } + }; + + const handleColumnsPreferenceSelection = () => { + const exportPreferenceSelection = document.getElementById(columnsPreferenceSelectId); + const selectedPreference = exportPreferenceSelection.options[exportPreferenceSelection.selectedIndex].value; + switch (selectedPreference) { + case exportPreferences.all: + /* */ + break; + case exportPreferences.selected: + /* */ + break; + case exportPreferences.notSelected: + /* */ + break; + case exportPreferences.visible: + /* */ + break; + default: + break; + } + }; + + return h('.p-1rem', [ + h('.flex.p-bottom-1rem.justify-center.items-center', + h('.settings-40-primary'), + h('.p-left-1rem', title)), + + h('.flex-wrap.justify-between.items-center', + h('.text-dark-blue', 'Rows'), + h('select.select.color-theme', { + id: rowsPreferenceSelectId, + name: rowsPreferenceSelectId, + onchange: () => handleRowsPreferenceSelection(), + }, [ + h('option', { value: exportPreferences.all }, 'All'), + h('option', { value: exportPreferences.currentPage }, 'Current page'), + h('option', { value: exportPreferences.selected }, 'Selected rows'), + h('option', { value: exportPreferences.notSelected }, 'Not selected rows'), + h('option', { value: exportPreferences.visible }, 'Visible rows'), + ], iconChevronBottom())), + + h('.flex-wrap.justify-between.items-center', + h('.text-dark-blue', 'Columns'), + h('select.select.color-theme', { + id: rowsPreferenceSelectId, + name: rowsPreferenceSelectId, + onchange: () => handleColumnsPreferenceSelection(), + }, [ + h('option', { value: exportPreferences.all }, 'All'), + h('option', { value: exportPreferences.selected }, 'Selected columns'), + h('option', { value: exportPreferences.notSelected }, 'Not selected columns'), + h('option', { value: exportPreferences.notSelected }, 'Visible columns'), + ], iconChevronBottom())), + + h('.flex-wrap.justify-center.items-center.p-1rem.p-bottom-0', + h('button.btn.btn-primary', { + onclick: () => exportData(), + }, 'Apply changes')), + ]); +} From b3b12ab48184f70301a2451f6f94686fc6f3d5da Mon Sep 17 00:00:00 2001 From: Ehevi Date: Tue, 22 Aug 2023 15:30:27 +0200 Subject: [PATCH 46/82] Enable user to select csv or json export --- app/public/components/sidebar/sidebar.js | 6 +- app/public/utils/dataExport/export.js | 65 ++++++++ app/public/utils/utils.js | 10 ++ app/public/views/modal/modal.js | 4 +- app/public/views/periods/PeriodsModel.js | 25 +++ .../views/periods/overview/dataExport.js | 155 ++++++++++++++++++ .../views/periods/overview/periodsExport.js | 105 +----------- 7 files changed, 266 insertions(+), 104 deletions(-) create mode 100644 app/public/utils/dataExport/export.js create mode 100644 app/public/views/periods/overview/dataExport.js diff --git a/app/public/components/sidebar/sidebar.js b/app/public/components/sidebar/sidebar.js index 3cc097331..0bd9ee427 100644 --- a/app/public/components/sidebar/sidebar.js +++ b/app/public/components/sidebar/sidebar.js @@ -31,9 +31,9 @@ export default function sidebar(model) { id: 'sidebar', }, modal(modalIds.about.modal), - modal(modalIds.pageSettings.modal, model), - modal(modalIds.detectors.modal, model), - modal(modalIds.dataExport.modal, model), + modal(modalIds.pageSettings.modal, null, model), + modal(modalIds.detectors.modal, null, model), + modal(modalIds.dataExport.modal, model.parent.periods, model), h('.logo.ph3.hide-on-close'), h('.flex-column.gap-20', h('.sidebar-section', diff --git a/app/public/utils/dataExport/export.js b/app/public/utils/dataExport/export.js new file mode 100644 index 000000000..2548940c6 --- /dev/null +++ b/app/public/utils/dataExport/export.js @@ -0,0 +1,65 @@ +/** + * @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. + */ + +/** + * Download a file + * + * @param {Object} content The source content. + * @param {String} fileName The name of the file including the output format. + * @param {String} contentType The content type of the file. + * @return {void} + */ +const downloadFile = (content, fileName, contentType) => { + const a = document.createElement('a'); //eslint-disable-line + const file = new Blob([content], { type: contentType }); //eslint-disable-line + a.href = URL.createObjectURL(file); + a.download = fileName; + a.click(); +}; + +/** + * Create JSON export + * + * @param {Object} content The source content. + * @param {String} fileName The name of the file including the output format. + * @param {String} contentType The content type of the file. + * @return {void} + */ +const createJSONExport = (content, fileName, contentType) => { + const json = JSON.stringify(content, null, 2); + downloadFile(json, fileName, contentType); +}; + +/** + * Create CSV export + * + * @param {Object} content The source content. + * @param {String} fileName The name of the file including the output format. + * @param {String} contentType The content type of the file. + * @return {void} + */ +const createCSVExport = (content, fileName, contentType) => { + const items = content; + const replacer = (key, value) => value === null ? '' : value; //eslint-disable-line + const header = Object.keys(items[0]); + let csv = items.map((row) => header.map((fieldName) => JSON.stringify(row[fieldName], replacer))); + csv.unshift(header.join(',')); + csv = csv.join('\r\n'); + downloadFile(csv, fileName, contentType); +}; + +export { + downloadFile, + createJSONExport, + createCSVExport, +}; diff --git a/app/public/utils/utils.js b/app/public/utils/utils.js index 6a568b9bb..a613b14f4 100644 --- a/app/public/utils/utils.js +++ b/app/public/utils/utils.js @@ -24,3 +24,13 @@ export function getReadableFileSizeString(fileSizeInBytes) { return `${Math.max(fileSizeInBytes, 0.1).toFixed(1)} ${byteUnits[i]}`; } + +/** + * Returns an object composed of the picked object properties + * + * @param {Object} obj The source object. + * @param {Array} keys The property paths to pick. + * @returns {Object} Returns the new object. + */ +export const pick = (obj, keys) => Object.fromEntries(Object.entries(obj) + .filter(([key]) => keys.includes(key))); diff --git a/app/public/views/modal/modal.js b/app/public/views/modal/modal.js index 076e0b7cb..f9511aae2 100644 --- a/app/public/views/modal/modal.js +++ b/app/public/views/modal/modal.js @@ -64,7 +64,7 @@ export const showModal = (modalId) => { } }; -export const modal = (modalId, model = null) => { +export const modal = (modalId, dataModel = null, model = null) => { switch (modalId) { case modalIds.pageSettings.modal: { return model @@ -94,7 +94,7 @@ export const modal = (modalId, model = null) => { id: modalIds.dataExport.content, }, periodsExport(model.parent.userPreferences, () => { document.getElementById(modalIds.dataExport.modal).style.display = 'none'; - }))); + }, dataModel))); } } }; diff --git a/app/public/views/periods/PeriodsModel.js b/app/public/views/periods/PeriodsModel.js index b516bce69..594e3804d 100644 --- a/app/public/views/periods/PeriodsModel.js +++ b/app/public/views/periods/PeriodsModel.js @@ -15,6 +15,8 @@ import { Observable, RemoteData } from '/js/src/index.js'; import { PaginationModel } from '../../components/table/pagination/PaginationModel.js'; import { getRemoteDataSlice } from '../../utils/fetch/getRemoteDataSlice.js'; import { RCT } from '../../config.js'; +import { createCSVExport, createJSONExport } from '../../utils/dataExport/export.js'; +import { exportFormats } from './overview/dataExport.js'; /** * Model representing handlers for periods page @@ -119,6 +121,29 @@ export default class PeriodsModel extends Observable { await this.fetchCurrentPagePeriods(); } + /** + * Create the export with the variables set in the model, handling errors appropriately + * @param {Object} content The source content. + * @param {String} fileName The name of the file + * @param {String} exportType output data format + * @return {void} + */ + async createDataExport(content, fileName, exportType) { + if (content.length > 0) { + exportType === exportFormats.csv + ? createCSVExport(content, `${fileName}.csv`, 'text/csv;charset=utf-8;') + : createJSONExport(content, `${fileName}.json`, 'application/json'); + } else { + this._currentPagePeriods = RemoteData.failure([ + { + title: 'No data found', + detail: 'No valid periods were found', + }, + ]); + this.notify(); + } + } + /** * Get current page periods * @return {RemoteData} periods in the current page diff --git a/app/public/views/periods/overview/dataExport.js b/app/public/views/periods/overview/dataExport.js new file mode 100644 index 000000000..331e2396d --- /dev/null +++ b/app/public/views/periods/overview/dataExport.js @@ -0,0 +1,155 @@ +/** + * @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, iconChevronBottom } from '/js/src/index.js'; + +export const exportFormats = { + csv: 'CSV', + json: 'JSON', +}; + +export default function dataExport(userPreferences, close, pageName, dataModel) { + const rowsPreferenceSelectId = 'rows-preference-selection'; + const columnsPreferenceSelectId = 'columns-preference-selection'; + const exportFormatSelectId = 'export-format-selection'; + + const exportPreferences = { + all: 'all', + currentPage: 'currentPage', + selected: 'selected', + notSelected: 'notSelected', + visible: 'visible', + }; + + const title = h('h3.text-primary', `${pageName} export`); + + const exportData = async () => { + const remoteData = dataModel.currentPageData; + + remoteData.match({ + NotAsked: () => {}, + Loading: () => {}, + Success: async () => { + await dataModel.createDataExport(remoteData.payload, pageName, selectedDataFormat()); + }, + Failure: () => {}, + }); + close(); + }; + + const handleRowsPreferenceSelection = () => { + const exportPreferenceSelection = document.getElementById(rowsPreferenceSelectId); + const selectedPreference = exportPreferenceSelection.options[exportPreferenceSelection.selectedIndex].value; + switch (selectedPreference) { + case exportPreferences.all: + /* */ + break; + case exportPreferences.currentPage: + /* */ + break; + case exportPreferences.selected: + /* */ + break; + case exportPreferences.notSelected: + /* */ + break; + case exportPreferences.visible: + /* */ + break; + default: + break; + } + }; + + const handleColumnsPreferenceSelection = () => { + const exportPreferenceSelection = document.getElementById(columnsPreferenceSelectId); + const selectedPreference = exportPreferenceSelection.options[exportPreferenceSelection.selectedIndex].value; + switch (selectedPreference) { + case exportPreferences.all: + /* */ + break; + case exportPreferences.selected: + /* */ + break; + case exportPreferences.notSelected: + /* */ + break; + case exportPreferences.visible: + /* */ + break; + default: + break; + } + }; + + const selectedDataFormat = () => { + const exportFormatSelection = document.getElementById(exportFormatSelectId); + const selectedFormat = exportFormatSelection.options[exportFormatSelection.selectedIndex].value; + switch (selectedFormat) { + case exportFormats.csv: + return selectedFormat; + case exportFormats.json: + return selectedFormat; + default: + return selectedFormat.csv; + } + }; + + return h('.p-1rem', [ + h('.flex.p-bottom-1rem.items-center', + h('', title)), + + h('.flex-wrap.justify-between.items-center', + h('.text-dark-blue', 'Rows'), + h('select.select.color-theme', { + id: rowsPreferenceSelectId, + name: rowsPreferenceSelectId, + onchange: () => handleRowsPreferenceSelection(), + }, [ + h('option', { value: exportPreferences.all, disabled: true }, 'All'), + h('option', { value: exportPreferences.currentPage }, 'Current page'), + h('option', { value: exportPreferences.selected, disabled: true }, 'Selected rows'), + h('option', { value: exportPreferences.notSelected, disabled: true }, 'Not selected rows'), + h('option', { value: exportPreferences.visible, disabled: true }, 'Visible rows'), + ], iconChevronBottom())), + + h('.flex-wrap.justify-between.items-center', + h('.text-dark-blue', 'Columns'), + h('select.select.color-theme', { + id: rowsPreferenceSelectId, + name: rowsPreferenceSelectId, + onchange: () => handleColumnsPreferenceSelection(), + }, [ + h('option', { value: exportPreferences.all }, 'All'), + h('option', { value: exportPreferences.selected, disabled: true }, 'Selected columns'), + h('option', { value: exportPreferences.notSelected, disabled: true }, 'Not selected columns'), + h('option', { value: exportPreferences.notSelected, disabled: true }, 'Visible columns'), + ], iconChevronBottom())), + + h('.flex-wrap.justify-between.items-center', + h('.text-dark-blue', 'Data format'), + h('select.select.color-theme', { + id: exportFormatSelectId, + name: exportFormatSelectId, + }, [ + h('option', { value: exportFormats.csv }, exportFormats.csv), + h('option', { value: exportFormats.json }, exportFormats.json), + ], iconChevronBottom())), + + h('.flex-wrap.justify-center.items-center.p-1rem.p-bottom-0', + h('button.btn.btn-primary', { + onclick: async () => await exportData(), + }, 'Download file')), + ]); +} diff --git a/app/public/views/periods/overview/periodsExport.js b/app/public/views/periods/overview/periodsExport.js index 2da684492..5019cf9dd 100644 --- a/app/public/views/periods/overview/periodsExport.js +++ b/app/public/views/periods/overview/periodsExport.js @@ -12,104 +12,11 @@ * or submit itself to any jurisdiction. */ -import { h, iconChevronBottom } from '/js/src/index.js'; +import dataExport from './dataExport.js'; +import { RCT } from '../../../config.js'; +const { periods } = RCT.pageNames; -export default function periodsExport(userPreferences, close) { - const rowsPreferenceSelectId = 'rows-preference-selection'; - const columnsPreferenceSelectId = 'columns-preference-selection'; - const exportPreferences = { - all: 'all', - currentPage: 'currentPage', - selected: 'selected', - notSelected: 'notSelected', - visible: 'visible', - }; - const title = h('h3.text-primary', 'Export'); - - const exportData = () => { - close(); - }; - - const handleRowsPreferenceSelection = () => { - const exportPreferenceSelection = document.getElementById(rowsPreferenceSelectId); - const selectedPreference = exportPreferenceSelection.options[exportPreferenceSelection.selectedIndex].value; - switch (selectedPreference) { - case exportPreferences.all: - /* */ - break; - case exportPreferences.currentPage: - /* */ - break; - case exportPreferences.selected: - /* */ - break; - case exportPreferences.notSelected: - /* */ - break; - case exportPreferences.visible: - /* */ - break; - default: - break; - } - }; - - const handleColumnsPreferenceSelection = () => { - const exportPreferenceSelection = document.getElementById(columnsPreferenceSelectId); - const selectedPreference = exportPreferenceSelection.options[exportPreferenceSelection.selectedIndex].value; - switch (selectedPreference) { - case exportPreferences.all: - /* */ - break; - case exportPreferences.selected: - /* */ - break; - case exportPreferences.notSelected: - /* */ - break; - case exportPreferences.visible: - /* */ - break; - default: - break; - } - }; - - return h('.p-1rem', [ - h('.flex.p-bottom-1rem.justify-center.items-center', - h('.settings-40-primary'), - h('.p-left-1rem', title)), - - h('.flex-wrap.justify-between.items-center', - h('.text-dark-blue', 'Rows'), - h('select.select.color-theme', { - id: rowsPreferenceSelectId, - name: rowsPreferenceSelectId, - onchange: () => handleRowsPreferenceSelection(), - }, [ - h('option', { value: exportPreferences.all }, 'All'), - h('option', { value: exportPreferences.currentPage }, 'Current page'), - h('option', { value: exportPreferences.selected }, 'Selected rows'), - h('option', { value: exportPreferences.notSelected }, 'Not selected rows'), - h('option', { value: exportPreferences.visible }, 'Visible rows'), - ], iconChevronBottom())), - - h('.flex-wrap.justify-between.items-center', - h('.text-dark-blue', 'Columns'), - h('select.select.color-theme', { - id: rowsPreferenceSelectId, - name: rowsPreferenceSelectId, - onchange: () => handleColumnsPreferenceSelection(), - }, [ - h('option', { value: exportPreferences.all }, 'All'), - h('option', { value: exportPreferences.selected }, 'Selected columns'), - h('option', { value: exportPreferences.notSelected }, 'Not selected columns'), - h('option', { value: exportPreferences.notSelected }, 'Visible columns'), - ], iconChevronBottom())), - - h('.flex-wrap.justify-center.items-center.p-1rem.p-bottom-0', - h('button.btn.btn-primary', { - onclick: () => exportData(), - }, 'Apply changes')), - ]); +export default function periodsExport(userPreferences, close, periodsModel) { + // To do: move the "Capitalize the first letter" function to utils and test it + return dataExport(userPreferences, close, periods.charAt(0).toUpperCase() + periods.slice(1), periodsModel); } From bf04c25b32d4be04935c043469ef55e89cd75558 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 23 Aug 2023 11:51:27 +0200 Subject: [PATCH 47/82] Cleanup :broom: --- .../dataProcessing/dataProcessingUtils.js | 22 ++++++++++++ app/public/utils/utils.js | 36 ------------------- .../views/periods/overview/dataExport.js | 16 ++++----- .../views/userView/data/pagesCellsSpecials.js | 2 +- 4 files changed, 31 insertions(+), 45 deletions(-) delete mode 100644 app/public/utils/utils.js diff --git a/app/public/utils/dataProcessing/dataProcessingUtils.js b/app/public/utils/dataProcessing/dataProcessingUtils.js index 69c72d0b3..382ee7dae 100644 --- a/app/public/utils/dataProcessing/dataProcessingUtils.js +++ b/app/public/utils/dataProcessing/dataProcessingUtils.js @@ -41,3 +41,25 @@ export const rowDisplayStyle = (isMarked, shouldHideMarkedRecords) => isMarked ? '.none' : '.row-selected' : '.row-not-selected'; + +export function getReadableFileSizeString(fileSizeInBytes) { + let i = -1; + const byteUnits = ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + do { + fileSizeInBytes /= 1024; + i++; + } while (fileSizeInBytes > 1024); + + return `${Math.max(fileSizeInBytes, 0.1).toFixed(1)} ${byteUnits[i]}`; +} + +/** + * Returns an object composed of the picked object properties + * + * @param {Object} obj The source object. + * @param {Array} keys The property paths to pick. + * @returns {Object} Returns the new object. + */ +export const pick = (obj, keys) => Object.fromEntries(Object.entries(obj) + .filter(([key]) => keys.includes(key))); +// ToDo: add tests diff --git a/app/public/utils/utils.js b/app/public/utils/utils.js deleted file mode 100644 index a613b14f4..000000000 --- a/app/public/utils/utils.js +++ /dev/null @@ -1,36 +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. - */ - -export const zip = (a, b) => Array.from(Array(Math.max(b.length, a.length)), (_, i) => [a[i], b[i]]); - -export function getReadableFileSizeString(fileSizeInBytes) { - let i = -1; - const byteUnits = ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; - do { - fileSizeInBytes /= 1024; - i++; - } while (fileSizeInBytes > 1024); - - return `${Math.max(fileSizeInBytes, 0.1).toFixed(1)} ${byteUnits[i]}`; -} - -/** - * Returns an object composed of the picked object properties - * - * @param {Object} obj The source object. - * @param {Array} keys The property paths to pick. - * @returns {Object} Returns the new object. - */ -export const pick = (obj, keys) => Object.fromEntries(Object.entries(obj) - .filter(([key]) => keys.includes(key))); diff --git a/app/public/views/periods/overview/dataExport.js b/app/public/views/periods/overview/dataExport.js index 331e2396d..f51b68610 100644 --- a/app/public/views/periods/overview/dataExport.js +++ b/app/public/views/periods/overview/dataExport.js @@ -19,19 +19,19 @@ export const exportFormats = { json: 'JSON', }; +export const exportPreferences = { + all: 'all', + currentPage: 'currentPage', + selected: 'selected', + notSelected: 'notSelected', + visible: 'visible', +}; + export default function dataExport(userPreferences, close, pageName, dataModel) { const rowsPreferenceSelectId = 'rows-preference-selection'; const columnsPreferenceSelectId = 'columns-preference-selection'; const exportFormatSelectId = 'export-format-selection'; - const exportPreferences = { - all: 'all', - currentPage: 'currentPage', - selected: 'selected', - notSelected: 'notSelected', - visible: 'visible', - }; - const title = h('h3.text-primary', `${pageName} export`); const exportData = async () => { diff --git a/app/public/views/userView/data/pagesCellsSpecials.js b/app/public/views/userView/data/pagesCellsSpecials.js index 51b467849..0f95f3964 100644 --- a/app/public/views/userView/data/pagesCellsSpecials.js +++ b/app/public/views/userView/data/pagesCellsSpecials.js @@ -13,7 +13,7 @@ */ import { h } from '/js/src/index.js'; import { RCT } from '../../../config.js'; -import { getReadableFileSizeString } from '../../../utils/utils.js'; +import { getReadableFileSizeString } from '../../../utils/dataProcessing/dataProcessingUtils.js'; import linkChip from '../../../components/chips/linkChip.js'; import { getClosestDefinedEnergy } from '../../../utils/dataProcessing/dataProcessingUtils.js'; const { dataReqParams: DRP, pageNames: PN, outerServices } = RCT; From 5b47e18b99ce43155fdbcfb0057a5a2fd201be26 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 23 Aug 2023 15:26:13 +0200 Subject: [PATCH 48/82] Mark obsolete csv download as deprecated --- app/public/utils/obsoleteCsvExport.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/public/utils/obsoleteCsvExport.js b/app/public/utils/obsoleteCsvExport.js index f3e654f6f..628d0358f 100644 --- a/app/public/utils/obsoleteCsvExport.js +++ b/app/public/utils/obsoleteCsvExport.js @@ -39,6 +39,13 @@ export const preparedFile = (model) => { const replacer = (key, value) => value === null ? '' : value; +/** + * Uses deprecated data model. + * Please use the new csv export with the specific dataModel (e.g. `periodsModel`) instead. + * @deprecated + * @param {DataAccessModel} model dataAccessModel + * @returns {void} downloads the CSV file with the current data + */ export default function obsoleteDownloadCSV(model) { const file = preparedFile(model); const link = document.createElement('a'); From f8becf770944c0debab582db5dfd0868ccaa9f96 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 23 Aug 2023 15:35:46 +0200 Subject: [PATCH 49/82] Pass userPreferences directly to modals --- app/public/components/sidebar/sidebar.js | 7 +++--- app/public/views/modal/modal.js | 30 ++++++++++++++---------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/app/public/components/sidebar/sidebar.js b/app/public/components/sidebar/sidebar.js index 0bd9ee427..d92c7a85b 100644 --- a/app/public/components/sidebar/sidebar.js +++ b/app/public/components/sidebar/sidebar.js @@ -27,13 +27,14 @@ const { pageNames } = RCT; */ export default function sidebar(model) { + const { userPreferences } = model.parent; return h('.sidebar.sidebar-collapsible', { id: 'sidebar', }, modal(modalIds.about.modal), - modal(modalIds.pageSettings.modal, null, model), - modal(modalIds.detectors.modal, null, model), - modal(modalIds.dataExport.modal, model.parent.periods, model), + modal(modalIds.pageSettings.modal, null, userPreferences), + modal(modalIds.detectors.modal, null, userPreferences), + modal(modalIds.dataExport.modal, model.parent.periods, userPreferences), h('.logo.ph3.hide-on-close'), h('.flex-column.gap-20', h('.sidebar-section', diff --git a/app/public/views/modal/modal.js b/app/public/views/modal/modal.js index f9511aae2..41bae8b72 100644 --- a/app/public/views/modal/modal.js +++ b/app/public/views/modal/modal.js @@ -64,14 +64,14 @@ export const showModal = (modalId) => { } }; -export const modal = (modalId, dataModel = null, model = null) => { +export const modal = (modalId, dataModel = null, userPreferences = null) => { switch (modalId) { case modalIds.pageSettings.modal: { - return model + return userPreferences ? h(`.${modalClassNames.modal}`, { id: modalIds.pageSettings.modal }, h(`.${modalClassNames.content}.abs-center.p3`, { id: modalIds.pageSettings.content, - }, pageSettings(model.parent.userPreferences, () => { + }, pageSettings(userPreferences, () => { document.getElementById(modalIds.pageSettings.modal).style.display = 'none'; }))) : ''; @@ -83,18 +83,22 @@ export const modal = (modalId, dataModel = null, model = null) => { }, about())); } case modalIds.detectors.modal: { - return h(`.${modalClassNames.modal}`, { id: modalIds.detectors.modal }, - h(`.${modalClassNames.content}.abs-center.p3`, { - id: modalIds.detectors.content, - }, detectorSettings(model.parent.userPreferences))); + return userPreferences + ? h(`.${modalClassNames.modal}`, { id: modalIds.detectors.modal }, + h(`.${modalClassNames.content}.abs-center.p3`, { + id: modalIds.detectors.content, + }, detectorSettings(userPreferences))) + : ''; } case modalIds.dataExport.modal: { - return h(`.${modalClassNames.modal}`, { id: modalIds.dataExport.modal }, - h(`.${modalClassNames.content}.abs-center.p3`, { - id: modalIds.dataExport.content, - }, periodsExport(model.parent.userPreferences, () => { - document.getElementById(modalIds.dataExport.modal).style.display = 'none'; - }, dataModel))); + return userPreferences + ? h(`.${modalClassNames.modal}`, { id: modalIds.dataExport.modal }, + h(`.${modalClassNames.content}.abs-center.p3`, { + id: modalIds.dataExport.content, + }, periodsExport(userPreferences, () => { + document.getElementById(modalIds.dataExport.modal).style.display = 'none'; + }, dataModel))) + : ''; } } }; From 4ab63c8c84f6f58420fac15ac601df21e08e4f63 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 23 Aug 2023 15:37:55 +0200 Subject: [PATCH 50/82] Cleanup :broom: --- app/public/utils/dataExport/export.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/public/utils/dataExport/export.js b/app/public/utils/dataExport/export.js index 2548940c6..47f4b4b0f 100644 --- a/app/public/utils/dataExport/export.js +++ b/app/public/utils/dataExport/export.js @@ -20,8 +20,8 @@ * @return {void} */ const downloadFile = (content, fileName, contentType) => { - const a = document.createElement('a'); //eslint-disable-line - const file = new Blob([content], { type: contentType }); //eslint-disable-line + const a = document.createElement('a'); + const file = new Blob([content], { type: contentType }); a.href = URL.createObjectURL(file); a.download = fileName; a.click(); @@ -50,7 +50,7 @@ const createJSONExport = (content, fileName, contentType) => { */ const createCSVExport = (content, fileName, contentType) => { const items = content; - const replacer = (key, value) => value === null ? '' : value; //eslint-disable-line + const replacer = (_key, value) => value === null ? '' : value; const header = Object.keys(items[0]); let csv = items.map((row) => header.map((fieldName) => JSON.stringify(row[fieldName], replacer))); csv.unshift(header.join(',')); From 384dc79aa32373bc2783cbe0441aae768a652cfb Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 23 Aug 2023 15:41:14 +0200 Subject: [PATCH 51/82] Cleanup :broom: --- .../utils/dataProcessing/dataProcessingUtils.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/app/public/utils/dataProcessing/dataProcessingUtils.js b/app/public/utils/dataProcessing/dataProcessingUtils.js index 382ee7dae..112e6b044 100644 --- a/app/public/utils/dataProcessing/dataProcessingUtils.js +++ b/app/public/utils/dataProcessing/dataProcessingUtils.js @@ -52,14 +52,3 @@ export function getReadableFileSizeString(fileSizeInBytes) { return `${Math.max(fileSizeInBytes, 0.1).toFixed(1)} ${byteUnits[i]}`; } - -/** - * Returns an object composed of the picked object properties - * - * @param {Object} obj The source object. - * @param {Array} keys The property paths to pick. - * @returns {Object} Returns the new object. - */ -export const pick = (obj, keys) => Object.fromEntries(Object.entries(obj) - .filter(([key]) => keys.includes(key))); -// ToDo: add tests From 606f3c77b7fc032339daa57839d1c78877e930eb Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 23 Aug 2023 15:48:51 +0200 Subject: [PATCH 52/82] Remove dependencies --- app/public/views/modal/modal.js | 4 ++-- app/public/views/periods/PeriodsModel.js | 1 + app/public/views/periods/overview/dataExport.js | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/public/views/modal/modal.js b/app/public/views/modal/modal.js index 41bae8b72..a5725430a 100644 --- a/app/public/views/modal/modal.js +++ b/app/public/views/modal/modal.js @@ -16,7 +16,7 @@ import { h } from '/js/src/index.js'; import pageSettings from '../userView/data/pageSettings/pageSettings.js'; import about from '../../components/about/about.js'; import detectorSettings from '../userView/data/detectorSettings/detectorSettings.js'; -import periodsExport from '../periods/overview/periodsExport.js'; +import dataExport from '../periods/overview/dataExport.js'; export const modalClassNames = { modal: 'modal', @@ -95,7 +95,7 @@ export const modal = (modalId, dataModel = null, userPreferences = null) => { ? h(`.${modalClassNames.modal}`, { id: modalIds.dataExport.modal }, h(`.${modalClassNames.content}.abs-center.p3`, { id: modalIds.dataExport.content, - }, periodsExport(userPreferences, () => { + }, dataExport(() => { document.getElementById(modalIds.dataExport.modal).style.display = 'none'; }, dataModel))) : ''; diff --git a/app/public/views/periods/PeriodsModel.js b/app/public/views/periods/PeriodsModel.js index 594e3804d..105c7f3c7 100644 --- a/app/public/views/periods/PeriodsModel.js +++ b/app/public/views/periods/PeriodsModel.js @@ -32,6 +32,7 @@ export default class PeriodsModel extends Observable { constructor(model) { super(); this.model = model; + this.name = RCT.pageNames.periods.charAt(0).toUpperCase() + RCT.pageNames.periods.slice(1); this._pagination = new PaginationModel(model.userPreferences); this._pagination.observe(() => { diff --git a/app/public/views/periods/overview/dataExport.js b/app/public/views/periods/overview/dataExport.js index f51b68610..f70e51629 100644 --- a/app/public/views/periods/overview/dataExport.js +++ b/app/public/views/periods/overview/dataExport.js @@ -27,7 +27,8 @@ export const exportPreferences = { visible: 'visible', }; -export default function dataExport(userPreferences, close, pageName, dataModel) { +export default function dataExport(close, dataModel) { + const pageName = dataModel.name; const rowsPreferenceSelectId = 'rows-preference-selection'; const columnsPreferenceSelectId = 'columns-preference-selection'; const exportFormatSelectId = 'export-format-selection'; From d54b1db8b78c3f4fe63abf779c493b8acc0a62ef Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 23 Aug 2023 16:48:43 +0200 Subject: [PATCH 53/82] Fix preparedData in obsoleteCsvExport --- app/public/utils/obsoleteCsvExport.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/public/utils/obsoleteCsvExport.js b/app/public/utils/obsoleteCsvExport.js index 628d0358f..b7ebf2765 100644 --- a/app/public/utils/obsoleteCsvExport.js +++ b/app/public/utils/obsoleteCsvExport.js @@ -13,10 +13,8 @@ */ export const preparedData = (data) => { - let rows = data.payload.rows.filter((item) => item.marked); - if (!rows) { - ({ rows } = data.payload); - } + const filteredRows = data.payload.rows.filter((item) => item.marked); + const rows = filteredRows.length > 0 ? filteredRows : data.payload.rows; const fields = data.payload.fields.filter((item) => item.marked).map((field) => field.name); let csv = rows.map((row) => fields.map((field) => JSON.stringify(row[field], replacer)).join(',')); From 1e4b1c9f699a7a09624dbd0f738275575702dc75 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 23 Aug 2023 16:51:01 +0200 Subject: [PATCH 54/82] Cleanup :broom: --- app/public/utils/obsoleteCsvExport.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/public/utils/obsoleteCsvExport.js b/app/public/utils/obsoleteCsvExport.js index b7ebf2765..08d6be905 100644 --- a/app/public/utils/obsoleteCsvExport.js +++ b/app/public/utils/obsoleteCsvExport.js @@ -35,7 +35,7 @@ export const preparedFile = (model) => { return { uri: encodeURI(csvContent), fileName: fileName }; }; -const replacer = (key, value) => value === null ? '' : value; +const replacer = (_key, value) => value === null ? '' : value; /** * Uses deprecated data model. From 0fbf57a2224c4b81c1736d3f92d1d2b55d949300 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 23 Aug 2023 16:56:04 +0200 Subject: [PATCH 55/82] Use the model name as downloaded filename for exports --- app/public/views/periods/PeriodsModel.js | 7 +++---- app/public/views/periods/overview/dataExport.js | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/public/views/periods/PeriodsModel.js b/app/public/views/periods/PeriodsModel.js index 105c7f3c7..e13bf9182 100644 --- a/app/public/views/periods/PeriodsModel.js +++ b/app/public/views/periods/PeriodsModel.js @@ -125,15 +125,14 @@ export default class PeriodsModel extends Observable { /** * Create the export with the variables set in the model, handling errors appropriately * @param {Object} content The source content. - * @param {String} fileName The name of the file * @param {String} exportType output data format * @return {void} */ - async createDataExport(content, fileName, exportType) { + async createDataExport(content, exportType) { if (content.length > 0) { exportType === exportFormats.csv - ? createCSVExport(content, `${fileName}.csv`, 'text/csv;charset=utf-8;') - : createJSONExport(content, `${fileName}.json`, 'application/json'); + ? createCSVExport(content, `${this.name.toLowerCase()}.csv`, 'text/csv;charset=utf-8;') + : createJSONExport(content, `${this.name.toLowerCase()}.json`, 'application/json'); } else { this._currentPagePeriods = RemoteData.failure([ { diff --git a/app/public/views/periods/overview/dataExport.js b/app/public/views/periods/overview/dataExport.js index f70e51629..4f5e44360 100644 --- a/app/public/views/periods/overview/dataExport.js +++ b/app/public/views/periods/overview/dataExport.js @@ -42,7 +42,7 @@ export default function dataExport(close, dataModel) { NotAsked: () => {}, Loading: () => {}, Success: async () => { - await dataModel.createDataExport(remoteData.payload, pageName, selectedDataFormat()); + await dataModel.createDataExport(remoteData.payload, selectedDataFormat()); }, Failure: () => {}, }); From acda97710052451df92861501796315397d9d2c8 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 23 Aug 2023 17:08:21 +0200 Subject: [PATCH 56/82] Add capitalization tests --- .../utils/dataProcessing/dataProcessingUtils.js | 6 ++++-- app/public/views/periods/PeriodsModel.js | 3 ++- test/public/utils/dataProcessing.test.js | 17 ++++++++++++++++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/app/public/utils/dataProcessing/dataProcessingUtils.js b/app/public/utils/dataProcessing/dataProcessingUtils.js index 112e6b044..e2bef2f0f 100644 --- a/app/public/utils/dataProcessing/dataProcessingUtils.js +++ b/app/public/utils/dataProcessing/dataProcessingUtils.js @@ -42,7 +42,7 @@ export const rowDisplayStyle = (isMarked, shouldHideMarkedRecords) => isMarked : '.row-selected' : '.row-not-selected'; -export function getReadableFileSizeString(fileSizeInBytes) { +export const getReadableFileSizeString = (fileSizeInBytes) => { let i = -1; const byteUnits = ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; do { @@ -51,4 +51,6 @@ export function getReadableFileSizeString(fileSizeInBytes) { } while (fileSizeInBytes > 1024); return `${Math.max(fileSizeInBytes, 0.1).toFixed(1)} ${byteUnits[i]}`; -} +}; + +export const capitalizeFirstLetter = (text) => text.charAt(0).toUpperCase() + text.slice(1); diff --git a/app/public/views/periods/PeriodsModel.js b/app/public/views/periods/PeriodsModel.js index e13bf9182..a351aeca4 100644 --- a/app/public/views/periods/PeriodsModel.js +++ b/app/public/views/periods/PeriodsModel.js @@ -17,6 +17,7 @@ import { getRemoteDataSlice } from '../../utils/fetch/getRemoteDataSlice.js'; import { RCT } from '../../config.js'; import { createCSVExport, createJSONExport } from '../../utils/dataExport/export.js'; import { exportFormats } from './overview/dataExport.js'; +import { capitalizeFirstLetter } from '../../utils/dataProcessing/dataProcessingUtils.js'; /** * Model representing handlers for periods page @@ -32,7 +33,7 @@ export default class PeriodsModel extends Observable { constructor(model) { super(); this.model = model; - this.name = RCT.pageNames.periods.charAt(0).toUpperCase() + RCT.pageNames.periods.slice(1); + this.name = capitalizeFirstLetter(RCT.pageNames.periods); this._pagination = new PaginationModel(model.userPreferences); this._pagination.observe(() => { diff --git a/test/public/utils/dataProcessing.test.js b/test/public/utils/dataProcessing.test.js index 7d219f530..5c3d6da98 100644 --- a/test/public/utils/dataProcessing.test.js +++ b/test/public/utils/dataProcessing.test.js @@ -18,7 +18,8 @@ const { extractPeriodName, detectorName, isDetectorField, shouldDisplayDetectorField, - rowDisplayStyle } = req('../../../app/public/utils/dataProcessing/dataProcessingUtils'); + rowDisplayStyle, + capitalizeFirstLetter } = req('../../../app/public/utils/dataProcessing/dataProcessingUtils'); module.exports = () => { describe('Extract period name', () => { @@ -133,4 +134,18 @@ module.exports = () => { assert(rowDisplayStyle(notSelected, shouldHideSelected) === rowNotSelected); }); }); + + describe('Capitalize the first letter', () => { + const sampleString1 = 'periods'; + const sampleString2 = '0periods'; + const expectedOutcome1 = 'Periods'; + + it('should capitalize the first letter of a given string' , () => { + assert(capitalizeFirstLetter(sampleString1) === expectedOutcome1 ); + }); + + it('should not affect the given string if the first char is not a letter' , () => { + assert(capitalizeFirstLetter(sampleString2) === sampleString2 ); + }); + }); }; From d2d7cd8218a696b351f966f91a168021b06cae03 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 23 Aug 2023 17:26:11 +0200 Subject: [PATCH 57/82] Add readable file size test --- .../utils/dataProcessing/dataProcessingUtils.js | 2 +- test/public/utils/dataProcessing.test.js | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/app/public/utils/dataProcessing/dataProcessingUtils.js b/app/public/utils/dataProcessing/dataProcessingUtils.js index e2bef2f0f..68f401a76 100644 --- a/app/public/utils/dataProcessing/dataProcessingUtils.js +++ b/app/public/utils/dataProcessing/dataProcessingUtils.js @@ -50,7 +50,7 @@ export const getReadableFileSizeString = (fileSizeInBytes) => { i++; } while (fileSizeInBytes > 1024); - return `${Math.max(fileSizeInBytes, 0.1).toFixed(1)} ${byteUnits[i]}`; + return `${Math.max(fileSizeInBytes, 0.1).toFixed(1)} ${byteUnits[i]}`; }; export const capitalizeFirstLetter = (text) => text.charAt(0).toUpperCase() + text.slice(1); diff --git a/test/public/utils/dataProcessing.test.js b/test/public/utils/dataProcessing.test.js index 5c3d6da98..cb297c750 100644 --- a/test/public/utils/dataProcessing.test.js +++ b/test/public/utils/dataProcessing.test.js @@ -19,7 +19,8 @@ const { extractPeriodName, isDetectorField, shouldDisplayDetectorField, rowDisplayStyle, - capitalizeFirstLetter } = req('../../../app/public/utils/dataProcessing/dataProcessingUtils'); + capitalizeFirstLetter, + getReadableFileSizeString } = req('../../../app/public/utils/dataProcessing/dataProcessingUtils'); module.exports = () => { describe('Extract period name', () => { @@ -148,4 +149,17 @@ module.exports = () => { assert(capitalizeFirstLetter(sampleString2) === sampleString2 ); }); }); + + describe('Check the readable file size', () => { + const fileSizekB = 1024; + const fileSizeGB = 3758096384; + + it('should parse kB correctly' , () => { + assert(getReadableFileSizeString(fileSizekB) === '1.0 kB' ); + }); + + it('should parse GB correctly' , () => { + assert(getReadableFileSizeString(fileSizeGB) === '3.5 GB' ); + }); + }); }; From 9104a20aa3c0f8fbf1841e95971b9e91eebe5df7 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Wed, 23 Aug 2023 17:39:54 +0200 Subject: [PATCH 58/82] Cleanup :broom: --- .../views/periods/overview/periodsExport.js | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 app/public/views/periods/overview/periodsExport.js diff --git a/app/public/views/periods/overview/periodsExport.js b/app/public/views/periods/overview/periodsExport.js deleted file mode 100644 index 5019cf9dd..000000000 --- a/app/public/views/periods/overview/periodsExport.js +++ /dev/null @@ -1,22 +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 dataExport from './dataExport.js'; -import { RCT } from '../../../config.js'; -const { periods } = RCT.pageNames; - -export default function periodsExport(userPreferences, close, periodsModel) { - // To do: move the "Capitalize the first letter" function to utils and test it - return dataExport(userPreferences, close, periods.charAt(0).toUpperCase() + periods.slice(1), periodsModel); -} From fdf861817624adfc6580c61b7326c2f1f9dd353f Mon Sep 17 00:00:00 2001 From: Ehevi Date: Thu, 24 Aug 2023 11:06:55 +0200 Subject: [PATCH 59/82] Cleanup :broom: --- app/public/utils/dataExport/export.js | 17 ++++---- test/public/utils/dataExport.test.js | 44 +++++++++++++++++++++ test/public/utils/index.js | 6 ++- test/public/utils/obsoleteCsvExport.test.js | 2 +- 4 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 test/public/utils/dataExport.test.js diff --git a/app/public/utils/dataExport/export.js b/app/public/utils/dataExport/export.js index 47f4b4b0f..7786291a1 100644 --- a/app/public/utils/dataExport/export.js +++ b/app/public/utils/dataExport/export.js @@ -49,17 +49,20 @@ const createJSONExport = (content, fileName, contentType) => { * @return {void} */ const createCSVExport = (content, fileName, contentType) => { - const items = content; - const replacer = (_key, value) => value === null ? '' : value; - const header = Object.keys(items[0]); - let csv = items.map((row) => header.map((fieldName) => JSON.stringify(row[fieldName], replacer))); - csv.unshift(header.join(',')); - csv = csv.join('\r\n'); + const csv = prepareCSVFile(content); downloadFile(csv, fileName, contentType); }; +const prepareCSVFile = (content) => { + const header = Object.keys(content[0]); + const csv = content.map((row) => header.map((fieldName) => JSON.stringify(row[fieldName], replacer))); + csv.unshift(header.join(',')); + return csv.join('\r\n'); +}; + +export const replacer = (_key, value) => value === null ? '' : value; + export { - downloadFile, createJSONExport, createCSVExport, }; diff --git a/test/public/utils/dataExport.test.js b/test/public/utils/dataExport.test.js new file mode 100644 index 000000000..77a7bdb55 --- /dev/null +++ b/test/public/utils/dataExport.test.js @@ -0,0 +1,44 @@ +/** + * @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 req = require('esm')(module) +const replacer = req('../../../app/public/utils/dataExport/export').replacer; +const assert = require('assert'); + +module.exports = () => { + describe('CSV Export', () => { + + describe('Replacer', () => { + const sampleObject = { + a: 1, + b: 2, + c: 'aa', + d: null, + e: 'bb', + f: 123, + }; + const expectedResult = { + a: 1, + b: 2, + c: 'aa', + d: '', + e: 'bb', + f: 123, + }; + + it('should replace null values with empty string', () => { + assert.deepEqual(Object.keys(sampleObject).reduce((acc, key) => ({...acc, [key]: replacer(key, sampleObject[key])}), {}), expectedResult); + }); + }); + }); +}; diff --git a/test/public/utils/index.js b/test/public/utils/index.js index 3a97df3b7..1bf057022 100644 --- a/test/public/utils/index.js +++ b/test/public/utils/index.js @@ -11,13 +11,15 @@ * or submit itself to any jurisdiction. */ -const CSVExportSuite = require('./obsoleteCsvExport.test'); +const obsoleteCSVExportSuite = require('./obsoleteCsvExport.test'); +const dataExportSuite = require('./dataExport.test'); const dataProcessingSuite = require('./dataProcessing.test'); const filterUtilsSuite = require('./filterUtils.test'); const urlUtilsSuite = require('./urlUtils.test'); module.exports = () => { - describe('CSV Export', CSVExportSuite); + describe('CSV Export (Obsolete)', obsoleteCSVExportSuite); + describe('Data Export', dataExportSuite); describe('Data processing', dataProcessingSuite); describe('Filter utils', filterUtilsSuite); describe('URL utils', urlUtilsSuite); diff --git a/test/public/utils/obsoleteCsvExport.test.js b/test/public/utils/obsoleteCsvExport.test.js index b4e6ae47d..f201c2e70 100644 --- a/test/public/utils/obsoleteCsvExport.test.js +++ b/test/public/utils/obsoleteCsvExport.test.js @@ -17,7 +17,7 @@ const preparedFile = req('../../../app/public/utils/obsoleteCsvExport').prepared const assert = require('assert'); module.exports = () => { - describe('CSV Export', () => { + describe('CSV Export (Obsolete)', () => { const dataSample = { payload: { rows: [ From 7ea3aceea8766019fc93cf5d306937e79c48450e Mon Sep 17 00:00:00 2001 From: Ehevi Date: Thu, 24 Aug 2023 11:21:05 +0200 Subject: [PATCH 60/82] Tests cleanup :broom: --- app/public/utils/dataExport/export.js | 14 ++++++++------ app/public/views/periods/PeriodsModel.js | 4 ++-- test/public/utils/dataExport.test.js | 15 ++++++++++++++- test/public/utils/obsoleteCsvExport.test.js | 4 ++-- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/app/public/utils/dataExport/export.js b/app/public/utils/dataExport/export.js index 7786291a1..39076a11b 100644 --- a/app/public/utils/dataExport/export.js +++ b/app/public/utils/dataExport/export.js @@ -35,9 +35,10 @@ const downloadFile = (content, fileName, contentType) => { * @param {String} contentType The content type of the file. * @return {void} */ -const createJSONExport = (content, fileName, contentType) => { +const createJSONExport = (content, fileName) => { const json = JSON.stringify(content, null, 2); - downloadFile(json, fileName, contentType); + const contentType = 'application/json'; + downloadFile(json, `${fileName}.json`, contentType); }; /** @@ -48,12 +49,13 @@ const createJSONExport = (content, fileName, contentType) => { * @param {String} contentType The content type of the file. * @return {void} */ -const createCSVExport = (content, fileName, contentType) => { - const csv = prepareCSVFile(content); - downloadFile(csv, fileName, contentType); +const createCSVExport = (content, fileName) => { + const contentType = 'text/csv;charset=utf-8;'; + const csv = prepareCSVContent(content); + downloadFile(csv, `${fileName}.csv`, contentType); }; -const prepareCSVFile = (content) => { +const prepareCSVContent = (content) => { const header = Object.keys(content[0]); const csv = content.map((row) => header.map((fieldName) => JSON.stringify(row[fieldName], replacer))); csv.unshift(header.join(',')); diff --git a/app/public/views/periods/PeriodsModel.js b/app/public/views/periods/PeriodsModel.js index a351aeca4..bcb3cccbe 100644 --- a/app/public/views/periods/PeriodsModel.js +++ b/app/public/views/periods/PeriodsModel.js @@ -132,8 +132,8 @@ export default class PeriodsModel extends Observable { async createDataExport(content, exportType) { if (content.length > 0) { exportType === exportFormats.csv - ? createCSVExport(content, `${this.name.toLowerCase()}.csv`, 'text/csv;charset=utf-8;') - : createJSONExport(content, `${this.name.toLowerCase()}.json`, 'application/json'); + ? createCSVExport(content, this.name.toLowerCase()) + : createJSONExport(content, this.name.toLowerCase()); } else { this._currentPagePeriods = RemoteData.failure([ { diff --git a/test/public/utils/dataExport.test.js b/test/public/utils/dataExport.test.js index 77a7bdb55..c28f47ed4 100644 --- a/test/public/utils/dataExport.test.js +++ b/test/public/utils/dataExport.test.js @@ -12,7 +12,7 @@ */ const req = require('esm')(module) -const replacer = req('../../../app/public/utils/dataExport/export').replacer; +const { replacer } = req('../../../app/public/utils/dataExport/export'); const assert = require('assert'); module.exports = () => { @@ -40,5 +40,18 @@ module.exports = () => { assert.deepEqual(Object.keys(sampleObject).reduce((acc, key) => ({...acc, [key]: replacer(key, sampleObject[key])}), {}), expectedResult); }); }); + + /* + describe('Check data preparation', () => { + it('should not return null', () => { + assert(preparedData(dataSample) !== null); + }); + + it('should filter values properly', () => { + assert(preparedData(dataSample).indexOf('b field') === -1); + assert(preparedData(dataSample).indexOf(3) === -1); + }); + }); + */ }); }; diff --git a/test/public/utils/obsoleteCsvExport.test.js b/test/public/utils/obsoleteCsvExport.test.js index f201c2e70..a47ecd2f4 100644 --- a/test/public/utils/obsoleteCsvExport.test.js +++ b/test/public/utils/obsoleteCsvExport.test.js @@ -12,8 +12,8 @@ */ const req = require('esm')(module) -const preparedData = req('../../../app/public/utils/obsoleteCsvExport').preparedData; -const preparedFile = req('../../../app/public/utils/obsoleteCsvExport').preparedFile; +const { preparedData } = req('../../../app/public/utils/obsoleteCsvExport'); +const { preparedFile } = req('../../../app/public/utils/obsoleteCsvExport'); const assert = require('assert'); module.exports = () => { From 66529b058a9b2dc906b80fa3608d8c82be2884c9 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Thu, 24 Aug 2023 11:43:00 +0200 Subject: [PATCH 61/82] Enum content types --- app/public/utils/dataExport/export.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/public/utils/dataExport/export.js b/app/public/utils/dataExport/export.js index 39076a11b..280cc0a7d 100644 --- a/app/public/utils/dataExport/export.js +++ b/app/public/utils/dataExport/export.js @@ -11,6 +11,11 @@ * or submit itself to any jurisdiction. */ +const contentTypes = { + json: 'application/json', + csv: 'text/csv;charset=utf-8;', +}; + /** * Download a file * @@ -32,13 +37,11 @@ const downloadFile = (content, fileName, contentType) => { * * @param {Object} content The source content. * @param {String} fileName The name of the file including the output format. - * @param {String} contentType The content type of the file. * @return {void} */ const createJSONExport = (content, fileName) => { const json = JSON.stringify(content, null, 2); - const contentType = 'application/json'; - downloadFile(json, `${fileName}.json`, contentType); + downloadFile(json, `${fileName}.json`, contentTypes.json); }; /** @@ -46,13 +49,11 @@ const createJSONExport = (content, fileName) => { * * @param {Object} content The source content. * @param {String} fileName The name of the file including the output format. - * @param {String} contentType The content type of the file. * @return {void} */ const createCSVExport = (content, fileName) => { - const contentType = 'text/csv;charset=utf-8;'; const csv = prepareCSVContent(content); - downloadFile(csv, `${fileName}.csv`, contentType); + downloadFile(csv, `${fileName}.csv`, contentTypes.csv); }; const prepareCSVContent = (content) => { From ca85ace80d0b723b8ee4d81118390a03073f9b77 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Thu, 24 Aug 2023 11:56:32 +0200 Subject: [PATCH 62/82] Add CSV data preparation test --- app/public/utils/dataExport/export.js | 4 +++- test/public/utils/dataExport.test.js | 32 +++++++++++++++++++++------ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/app/public/utils/dataExport/export.js b/app/public/utils/dataExport/export.js index 280cc0a7d..870694a67 100644 --- a/app/public/utils/dataExport/export.js +++ b/app/public/utils/dataExport/export.js @@ -56,10 +56,12 @@ const createCSVExport = (content, fileName) => { downloadFile(csv, `${fileName}.csv`, contentTypes.csv); }; -const prepareCSVContent = (content) => { +export const prepareCSVContent = (content) => { + console.log(content); const header = Object.keys(content[0]); const csv = content.map((row) => header.map((fieldName) => JSON.stringify(row[fieldName], replacer))); csv.unshift(header.join(',')); + console.log(csv.join('\r\n')); return csv.join('\r\n'); }; diff --git a/test/public/utils/dataExport.test.js b/test/public/utils/dataExport.test.js index c28f47ed4..f056fb088 100644 --- a/test/public/utils/dataExport.test.js +++ b/test/public/utils/dataExport.test.js @@ -12,7 +12,7 @@ */ const req = require('esm')(module) -const { replacer } = req('../../../app/public/utils/dataExport/export'); +const { replacer, prepareCSVContent } = req('../../../app/public/utils/dataExport/export'); const assert = require('assert'); module.exports = () => { @@ -41,17 +41,35 @@ module.exports = () => { }); }); - /* describe('Check data preparation', () => { + const rawData = [ + { + name: "LHC23zt", + year: 2023, + beamType: "p-p", + avgEnergy: 13595, + distinctEnergies: [6797.04, 6797.52, 6797.64, 6798], + }, + { + name: "LHC23zs", + year: 2023, + beamType: "p-p", + avgEnergy: 13596, + distinctEnergies: [6797.4, 6797.52, 6797.64, 6797.76, 6797.88, 6798, 6798.24, 6799.2], + } + ]; + + const preparedData = 'name,year,beamType,avgEnergy,distinctEnergies\r\n' + + `"LHC23zt",2023,"p-p",13595,[6797.04,6797.52,6797.64,6798]\r\n` + + `"LHC23zs",2023,"p-p",13596,[6797.4,6797.52,6797.64,6797.76,6797.88,6798,6798.24,6799.2]`; + it('should not return null', () => { - assert(preparedData(dataSample) !== null); + assert(prepareCSVContent(rawData) !== null); }); - it('should filter values properly', () => { - assert(preparedData(dataSample).indexOf('b field') === -1); - assert(preparedData(dataSample).indexOf(3) === -1); + it('should prepare CSV data content', () => { + assert.equal(prepareCSVContent(rawData), preparedData); }); }); - */ }); }; From 809176ac6f6a6ab7b5d01ae60405564c1eae69b6 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Thu, 24 Aug 2023 11:57:25 +0200 Subject: [PATCH 63/82] Remove console logs --- app/public/utils/dataExport/export.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/public/utils/dataExport/export.js b/app/public/utils/dataExport/export.js index 870694a67..d25b4ffe9 100644 --- a/app/public/utils/dataExport/export.js +++ b/app/public/utils/dataExport/export.js @@ -57,11 +57,9 @@ const createCSVExport = (content, fileName) => { }; export const prepareCSVContent = (content) => { - console.log(content); const header = Object.keys(content[0]); const csv = content.map((row) => header.map((fieldName) => JSON.stringify(row[fieldName], replacer))); csv.unshift(header.join(',')); - console.log(csv.join('\r\n')); return csv.join('\r\n'); }; From 4fdc9b50f3b2f12a6ccbd8f263acbc5c74dee8ac Mon Sep 17 00:00:00 2001 From: Ehevi Date: Thu, 24 Aug 2023 12:06:52 +0200 Subject: [PATCH 64/82] Fix dataExport modal rendering condition --- app/public/views/modal/modal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/public/views/modal/modal.js b/app/public/views/modal/modal.js index a5725430a..85a5004e8 100644 --- a/app/public/views/modal/modal.js +++ b/app/public/views/modal/modal.js @@ -91,7 +91,7 @@ export const modal = (modalId, dataModel = null, userPreferences = null) => { : ''; } case modalIds.dataExport.modal: { - return userPreferences + return dataModel ? h(`.${modalClassNames.modal}`, { id: modalIds.dataExport.modal }, h(`.${modalClassNames.content}.abs-center.p3`, { id: modalIds.dataExport.content, From 8c4d9d7e919758b398b38b84ec54f6e8133d35bd Mon Sep 17 00:00:00 2001 From: Ehevi Date: Thu, 24 Aug 2023 16:07:12 +0200 Subject: [PATCH 65/82] Address review comments --- .../utils/dataProcessing/dataProcessingUtils.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/public/utils/dataProcessing/dataProcessingUtils.js b/app/public/utils/dataProcessing/dataProcessingUtils.js index 68f401a76..8c8ab986f 100644 --- a/app/public/utils/dataProcessing/dataProcessingUtils.js +++ b/app/public/utils/dataProcessing/dataProcessingUtils.js @@ -42,15 +42,19 @@ export const rowDisplayStyle = (isMarked, shouldHideMarkedRecords) => isMarked : '.row-selected' : '.row-not-selected'; +/** + * Converts bytes into human readable file size string + * @param {number} fileSizeInBytes in bytes + * @returns string (human readable) + */ export const getReadableFileSizeString = (fileSizeInBytes) => { - let i = -1; const byteUnits = ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; - do { - fileSizeInBytes /= 1024; - i++; - } while (fileSizeInBytes > 1024); - return `${Math.max(fileSizeInBytes, 0.1).toFixed(1)} ${byteUnits[i]}`; + const result = byteUnits.reduce((acc, _current) => acc.fileSize > 1024 + ? { index: acc.index + 1, fileSize: acc.fileSize / 1024 } + : acc, { index: 0, fileSize: fileSizeInBytes / 1024 }); + + return `${Math.max(result.fileSize, 0.1).toFixed(1)} ${byteUnits[result.index]}`; }; export const capitalizeFirstLetter = (text) => text.charAt(0).toUpperCase() + text.slice(1); From e3e30a78c1ebf89d6c9d0b352003b88870d008ae Mon Sep 17 00:00:00 2001 From: Ehevi Date: Thu, 24 Aug 2023 16:09:41 +0200 Subject: [PATCH 66/82] Address review comments --- app/public/utils/dataExport/export.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/public/utils/dataExport/export.js b/app/public/utils/dataExport/export.js index d25b4ffe9..da6f6f33c 100644 --- a/app/public/utils/dataExport/export.js +++ b/app/public/utils/dataExport/export.js @@ -25,11 +25,11 @@ const contentTypes = { * @return {void} */ const downloadFile = (content, fileName, contentType) => { - const a = document.createElement('a'); + const link = document.createElement('a'); const file = new Blob([content], { type: contentType }); - a.href = URL.createObjectURL(file); - a.download = fileName; - a.click(); + link.href = URL.createObjectURL(file); + link.download = fileName; + link.click(); }; /** From f4aa38e691253647d0aa1bacda28c7a6193f1ba9 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Thu, 24 Aug 2023 16:45:13 +0200 Subject: [PATCH 67/82] Remove capitalizeFirstLetter function --- app/public/components/sidebar/sidebar.js | 18 ++++---- app/public/components/table/title.js | 18 +------- .../dataProcessing/dataProcessingUtils.js | 16 ++++++- app/public/views/periods/PeriodsModel.js | 4 +- test/public/utils/dataProcessing.test.js | 42 ++++++++++++------- 5 files changed, 54 insertions(+), 44 deletions(-) diff --git a/app/public/components/sidebar/sidebar.js b/app/public/components/sidebar/sidebar.js index d92c7a85b..856c05497 100644 --- a/app/public/components/sidebar/sidebar.js +++ b/app/public/components/sidebar/sidebar.js @@ -16,7 +16,7 @@ import { h } from '/js/src/index.js'; import { RCT } from '../../config.js'; import sidebarItem from './sidebarItem.js'; import { modal, modalIds, showModal } from '../../views/modal/modal.js'; -import { pageTitle } from '../table/title.js'; +import { pageTitle } from '../../utils/dataProcessing/dataProcessingUtils.js'; const { pageNames } = RCT; /** @@ -39,14 +39,14 @@ export default function sidebar(model) { h('.flex-column.gap-20', h('.sidebar-section', h('.sidebar-section-title.ph2.hide-on-close', 'Pages'), - sidebarItem(model, pageNames.periods, pageTitle(pageNames.periods)), - sidebarItem(model, pageNames.dataPasses, pageTitle(pageNames.dataPasses)), - sidebarItem(model, pageNames.anchoragePerDatapass, pageTitle(pageNames.anchoragePerDatapass)), - sidebarItem(model, pageNames.mc, pageTitle(pageNames.mc)), - sidebarItem(model, pageNames.anchoredPerMC, pageTitle(pageNames.anchoredPerMC)), - sidebarItem(model, pageNames.runsPerPeriod, pageTitle(pageNames.runsPerPeriod)), - sidebarItem(model, pageNames.runsPerDataPass, pageTitle(pageNames.runsPerDataPass)), - sidebarItem(model, pageNames.flags, pageTitle(pageNames.flags))), + sidebarItem(model, pageNames.periods, pageTitle(pageNames.periods, pageNames)), + sidebarItem(model, pageNames.dataPasses, pageTitle(pageNames.dataPasses, pageNames)), + sidebarItem(model, pageNames.anchoragePerDatapass, pageTitle(pageNames.anchoragePerDatapass, pageNames)), + sidebarItem(model, pageNames.mc, pageTitle(pageNames.mc, pageNames)), + sidebarItem(model, pageNames.anchoredPerMC, pageTitle(pageNames.anchoredPerMC, pageNames)), + sidebarItem(model, pageNames.runsPerPeriod, pageTitle(pageNames.runsPerPeriod, pageNames)), + sidebarItem(model, pageNames.runsPerDataPass, pageTitle(pageNames.runsPerDataPass, pageNames)), + sidebarItem(model, pageNames.flags, pageTitle(pageNames.flags, pageNames))), h('.sidebar-section', h('.sidebar-section-title.ph2.hide-on-close', 'Preferences'), diff --git a/app/public/components/table/title.js b/app/public/components/table/title.js index 7d3d36fa4..6f0549764 100644 --- a/app/public/components/table/title.js +++ b/app/public/components/table/title.js @@ -14,22 +14,8 @@ import { h } from '/js/src/index.js'; import { RCT } from '../../config.js'; -const { pageNames } = RCT; - -export function pageTitle(page) { - switch (page) { - case pageNames.periods: return 'Periods'; - case pageNames.runsPerPeriod: return 'Runs per period'; - case pageNames.runsPerDataPass: return 'Runs per data pass'; - case pageNames.dataPasses: return 'Data passes per period'; - case pageNames.mc: return 'Monte Carlo'; - case pageNames.flags: return 'Flags'; - case pageNames.anchoragePerDatapass: return 'Anchorage per data pass'; - case pageNames.anchoredPerMC: return 'Anchored per MC'; - default: return page; - } -} +import { pageTitle } from '../../utils/dataProcessing/dataProcessingUtils.js'; export default function title(page) { - return h('h3.p-right-15.text-primary', pageTitle(page)); + return h('h3.p-right-15.text-primary', pageTitle(page, RCT.pageNames)); } diff --git a/app/public/utils/dataProcessing/dataProcessingUtils.js b/app/public/utils/dataProcessing/dataProcessingUtils.js index 8c8ab986f..d7ebd2d2b 100644 --- a/app/public/utils/dataProcessing/dataProcessingUtils.js +++ b/app/public/utils/dataProcessing/dataProcessingUtils.js @@ -45,7 +45,7 @@ export const rowDisplayStyle = (isMarked, shouldHideMarkedRecords) => isMarked /** * Converts bytes into human readable file size string * @param {number} fileSizeInBytes in bytes - * @returns string (human readable) + * @returns {string} human readable file size */ export const getReadableFileSizeString = (fileSizeInBytes) => { const byteUnits = ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; @@ -57,4 +57,16 @@ export const getReadableFileSizeString = (fileSizeInBytes) => { return `${Math.max(result.fileSize, 0.1).toFixed(1)} ${byteUnits[result.index]}`; }; -export const capitalizeFirstLetter = (text) => text.charAt(0).toUpperCase() + text.slice(1); +export const pageTitle = (page, pageNames) => { + switch (page) { + case pageNames.periods: return 'Periods'; + case pageNames.runsPerPeriod: return 'Runs per period'; + case pageNames.runsPerDataPass: return 'Runs per data pass'; + case pageNames.dataPasses: return 'Data passes per period'; + case pageNames.mc: return 'Monte Carlo'; + case pageNames.flags: return 'Flags'; + case pageNames.anchoragePerDatapass: return 'Anchorage per data pass'; + case pageNames.anchoredPerMC: return 'Anchored per MC'; + default: return page; + } +}; diff --git a/app/public/views/periods/PeriodsModel.js b/app/public/views/periods/PeriodsModel.js index bcb3cccbe..3d3293883 100644 --- a/app/public/views/periods/PeriodsModel.js +++ b/app/public/views/periods/PeriodsModel.js @@ -17,7 +17,7 @@ import { getRemoteDataSlice } from '../../utils/fetch/getRemoteDataSlice.js'; import { RCT } from '../../config.js'; import { createCSVExport, createJSONExport } from '../../utils/dataExport/export.js'; import { exportFormats } from './overview/dataExport.js'; -import { capitalizeFirstLetter } from '../../utils/dataProcessing/dataProcessingUtils.js'; +import { pageTitle } from '../../utils/dataProcessing/dataProcessingUtils.js'; /** * Model representing handlers for periods page @@ -33,7 +33,7 @@ export default class PeriodsModel extends Observable { constructor(model) { super(); this.model = model; - this.name = capitalizeFirstLetter(RCT.pageNames.periods); + this.name = pageTitle(RCT.pageNames.periods, RCT.pageNames); this._pagination = new PaginationModel(model.userPreferences); this._pagination.observe(() => { diff --git a/test/public/utils/dataProcessing.test.js b/test/public/utils/dataProcessing.test.js index cb297c750..dab819d2f 100644 --- a/test/public/utils/dataProcessing.test.js +++ b/test/public/utils/dataProcessing.test.js @@ -20,7 +20,8 @@ const { extractPeriodName, shouldDisplayDetectorField, rowDisplayStyle, capitalizeFirstLetter, - getReadableFileSizeString } = req('../../../app/public/utils/dataProcessing/dataProcessingUtils'); + getReadableFileSizeString, + pageTitle } = req('../../../app/public/utils/dataProcessing/dataProcessingUtils'); module.exports = () => { describe('Extract period name', () => { @@ -136,20 +137,6 @@ module.exports = () => { }); }); - describe('Capitalize the first letter', () => { - const sampleString1 = 'periods'; - const sampleString2 = '0periods'; - const expectedOutcome1 = 'Periods'; - - it('should capitalize the first letter of a given string' , () => { - assert(capitalizeFirstLetter(sampleString1) === expectedOutcome1 ); - }); - - it('should not affect the given string if the first char is not a letter' , () => { - assert(capitalizeFirstLetter(sampleString2) === sampleString2 ); - }); - }); - describe('Check the readable file size', () => { const fileSizekB = 1024; const fileSizeGB = 3758096384; @@ -162,4 +149,29 @@ module.exports = () => { assert(getReadableFileSizeString(fileSizeGB) === '3.5 GB' ); }); }); + + describe('Page title', () => { + const pageNames = { + periods: 'periods', + dataPasses: 'dataPasses', + mc: 'mc', + anchoredPerMC: 'anchoredPerMC', + anchoragePerDatapass: 'anchoragePerDatapass', + runsPerPeriod: 'runsPerPeriod', + runsPerDataPass: 'runsPerDataPass', + flags: 'flags', + }; + + it('should return correct Periods page name' , () => { + assert.equal(pageTitle(pageNames.periods, pageNames), 'Periods'); + }); + + it('should return correct Data passes page name' , () => { + assert.equal(pageTitle(pageNames.dataPasses, pageNames), 'Data passes per period'); + }); + + it('should return correct Monte Carlo page name' , () => { + assert.equal(pageTitle(pageNames.mc, pageNames), 'Monte Carlo'); + }); + }); }; From 6950d888d883a1fd88fc5f1cb22cd6bc64cdd50c Mon Sep 17 00:00:00 2001 From: Ehevi Date: Thu, 24 Aug 2023 16:45:48 +0200 Subject: [PATCH 68/82] Newlines --- app/public/styles/rct/custom/components/pager.css | 1 - 1 file changed, 1 deletion(-) diff --git a/app/public/styles/rct/custom/components/pager.css b/app/public/styles/rct/custom/components/pager.css index 8c258c03c..e29cc11fc 100644 --- a/app/public/styles/rct/custom/components/pager.css +++ b/app/public/styles/rct/custom/components/pager.css @@ -41,4 +41,3 @@ margin: 3px; cursor: pointer; } - From 489c1d6e437822e7527599f4b3138bdcb7bff20b Mon Sep 17 00:00:00 2001 From: Ehevi Date: Thu, 24 Aug 2023 16:52:43 +0200 Subject: [PATCH 69/82] Pagename tests --- .../dataProcessing/dataProcessingUtils.js | 2 +- test/public/utils/dataProcessing.test.js | 26 ++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/app/public/utils/dataProcessing/dataProcessingUtils.js b/app/public/utils/dataProcessing/dataProcessingUtils.js index d7ebd2d2b..709c36406 100644 --- a/app/public/utils/dataProcessing/dataProcessingUtils.js +++ b/app/public/utils/dataProcessing/dataProcessingUtils.js @@ -64,7 +64,7 @@ export const pageTitle = (page, pageNames) => { case pageNames.runsPerDataPass: return 'Runs per data pass'; case pageNames.dataPasses: return 'Data passes per period'; case pageNames.mc: return 'Monte Carlo'; - case pageNames.flags: return 'Flags'; + case pageNames.flags: return 'Quality flags'; case pageNames.anchoragePerDatapass: return 'Anchorage per data pass'; case pageNames.anchoredPerMC: return 'Anchored per MC'; default: return page; diff --git a/test/public/utils/dataProcessing.test.js b/test/public/utils/dataProcessing.test.js index dab819d2f..08f00768c 100644 --- a/test/public/utils/dataProcessing.test.js +++ b/test/public/utils/dataProcessing.test.js @@ -162,16 +162,36 @@ module.exports = () => { flags: 'flags', }; - it('should return correct Periods page name' , () => { + it('should return Periods page name' , () => { assert.equal(pageTitle(pageNames.periods, pageNames), 'Periods'); }); - it('should return correct Data passes page name' , () => { + it('should return Data passes page name' , () => { assert.equal(pageTitle(pageNames.dataPasses, pageNames), 'Data passes per period'); }); - it('should return correct Monte Carlo page name' , () => { + it('should return Monte Carlo page name' , () => { assert.equal(pageTitle(pageNames.mc, pageNames), 'Monte Carlo'); }); + + it('should return Anchored per MC page name' , () => { + assert.equal(pageTitle(pageNames.anchoredPerMC, pageNames), 'Anchored per MC'); + }); + + it('should return Anchorage per data pass page name' , () => { + assert.equal(pageTitle(pageNames.anchoragePerDatapass, pageNames), 'Anchorage per data pass'); + }); + + it('should return Runs per period page name' , () => { + assert.equal(pageTitle(pageNames.runsPerPeriod, pageNames), 'Runs per period'); + }); + + it('should return Runs per data pass page name' , () => { + assert.equal(pageTitle(pageNames.runsPerDataPass, pageNames), 'Runs per data pass'); + }); + + it('should return Quality flags page name' , () => { + assert.equal(pageTitle(pageNames.flags, pageNames), 'Quality flags'); + }); }); }; From ac25f2f2eec47a48c26e099ed35453259f046ffc Mon Sep 17 00:00:00 2001 From: Ehevi Date: Thu, 24 Aug 2023 16:57:25 +0200 Subject: [PATCH 70/82] Cleanup :broom: --- app/public/components/buttons/copyLinkButton.js | 2 +- app/public/utils/sort.js | 2 +- app/public/views/modal/modal.js | 2 +- app/public/views/userView/data/table/tablePanel.js | 2 +- test/public/utils/dataProcessing.test.js | 12 ++++++------ 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/public/components/buttons/copyLinkButton.js b/app/public/components/buttons/copyLinkButton.js index 2f58cb54f..39864e52c 100644 --- a/app/public/components/buttons/copyLinkButton.js +++ b/app/public/components/buttons/copyLinkButton.js @@ -36,7 +36,7 @@ export default function copyLinkButton(url) { snackbar.style.display = 'flex'; document.addEventListener('click', (event) => { - if (thisButton != event.target && thisButtonContent != event.target) { + if (thisButton !== event.target && thisButtonContent !== event.target) { snackbar.style.display = 'none'; } }); diff --git a/app/public/utils/sort.js b/app/public/utils/sort.js index add75ea99..36f1b279c 100644 --- a/app/public/utils/sort.js +++ b/app/public/utils/sort.js @@ -14,7 +14,7 @@ import { switchCase } from '/js/src/index.js'; export const sort = (fName, data, model, order) => { - if (data.sorting.field != fName) { + if (data.sorting.field !== fName) { data.sorting.field = fName; data.sorting.order = null; } diff --git a/app/public/views/modal/modal.js b/app/public/views/modal/modal.js index 85a5004e8..6b6835286 100644 --- a/app/public/views/modal/modal.js +++ b/app/public/views/modal/modal.js @@ -53,7 +53,7 @@ export const showModal = (modalId) => { modal.style.display = 'block'; document.addEventListener('click', (event) => { const { modals, contents } = allModals(); - if (Array.from(contents).find((e) => e != event.target) + if (Array.from(contents).find((e) => e !== event.target) && Array.from(modals).find((e) => e == event.target) && document.getElementById(modalId)) { document.getElementById(modalId).style.display = 'none'; diff --git a/app/public/views/userView/data/table/tablePanel.js b/app/public/views/userView/data/table/tablePanel.js index b0cc44bc3..c9f570ee4 100644 --- a/app/public/views/userView/data/table/tablePanel.js +++ b/app/public/views/userView/data/table/tablePanel.js @@ -58,7 +58,7 @@ export default function tablePanel(model, runs) { .filter((index) => index !== defaultIndexString) .map((index) => indexChip(model, dataPointer.page, index)); - data.rows = data.rows.filter((item) => item.name != 'null'); + data.rows = data.rows.filter((item) => item.name !== 'null'); const cellsSpecials = pagesCellsSpecials[dataPointer.page]; diff --git a/test/public/utils/dataProcessing.test.js b/test/public/utils/dataProcessing.test.js index 08f00768c..e704144bb 100644 --- a/test/public/utils/dataProcessing.test.js +++ b/test/public/utils/dataProcessing.test.js @@ -121,19 +121,19 @@ module.exports = () => { const shouldNotHideSelected = false; it('should not display hidden rows', () => { - assert(rowDisplayStyle(selected, shouldHideSelected) === displayNone); + assert.equal(rowDisplayStyle(selected, shouldHideSelected), displayNone); }); it('should apply selection class to selected rows', () => { - assert(rowDisplayStyle(selected, shouldNotHideSelected) === rowSelected); + assert.equal(rowDisplayStyle(selected, shouldNotHideSelected), rowSelected); }); it('should apply corresponding class to unselected rows', () => { - assert(rowDisplayStyle(notSelected, shouldNotHideSelected) === rowNotSelected); + assert.equal(rowDisplayStyle(notSelected, shouldNotHideSelected), rowNotSelected); }); it('should apply corresponding class to unselected rows', () => { - assert(rowDisplayStyle(notSelected, shouldHideSelected) === rowNotSelected); + assert.equal(rowDisplayStyle(notSelected, shouldHideSelected), rowNotSelected); }); }); @@ -142,11 +142,11 @@ module.exports = () => { const fileSizeGB = 3758096384; it('should parse kB correctly' , () => { - assert(getReadableFileSizeString(fileSizekB) === '1.0 kB' ); + assert.equal(getReadableFileSizeString(fileSizekB), '1.0 kB' ); }); it('should parse GB correctly' , () => { - assert(getReadableFileSizeString(fileSizeGB) === '3.5 GB' ); + assert.equal(getReadableFileSizeString(fileSizeGB), '3.5 GB' ); }); }); From 85bb90aec3f4843681ceb391abd1cca1d8f08b60 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Thu, 24 Aug 2023 17:33:00 +0200 Subject: [PATCH 71/82] Sort cleanup --- app/public/utils/sort.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/public/utils/sort.js b/app/public/utils/sort.js index 36f1b279c..39f353569 100644 --- a/app/public/utils/sort.js +++ b/app/public/utils/sort.js @@ -11,16 +11,12 @@ * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. */ -import { switchCase } from '/js/src/index.js'; export const sort = (fName, data, model, order) => { if (data.sorting.field !== fName) { data.sorting.field = fName; data.sorting.order = null; } - data.sorting.order = data.sorting.order = switchCase(order, { - 1: 1, - '-1': -1, - }, null); + data.sorting.order = order; model.fetchedData.changeSorting(data.sorting); }; From c2e726729ec892cc3a9ae1663e4dbdeb33b90822 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Thu, 24 Aug 2023 17:46:42 +0200 Subject: [PATCH 72/82] Address review comments --- app/public/utils/dataExport/export.js | 2 +- app/public/utils/obsoleteCsvExport.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/public/utils/dataExport/export.js b/app/public/utils/dataExport/export.js index da6f6f33c..a848b05f6 100644 --- a/app/public/utils/dataExport/export.js +++ b/app/public/utils/dataExport/export.js @@ -63,7 +63,7 @@ export const prepareCSVContent = (content) => { return csv.join('\r\n'); }; -export const replacer = (_key, value) => value === null ? '' : value; +export const replacer = (_key, value) => value || ''; export { createJSONExport, diff --git a/app/public/utils/obsoleteCsvExport.js b/app/public/utils/obsoleteCsvExport.js index 08d6be905..8f1b61a72 100644 --- a/app/public/utils/obsoleteCsvExport.js +++ b/app/public/utils/obsoleteCsvExport.js @@ -35,7 +35,7 @@ export const preparedFile = (model) => { return { uri: encodeURI(csvContent), fileName: fileName }; }; -const replacer = (_key, value) => value === null ? '' : value; +const replacer = (_key, value) => value || ''; /** * Uses deprecated data model. From ad73985dc6feb40d548eff38f28901976914eb90 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Thu, 24 Aug 2023 17:54:34 +0200 Subject: [PATCH 73/82] Address review comments --- .../views/periods/overview/dataExport.js | 153 +++++++++--------- 1 file changed, 77 insertions(+), 76 deletions(-) diff --git a/app/public/views/periods/overview/dataExport.js b/app/public/views/periods/overview/dataExport.js index 4f5e44360..a5c2707d6 100644 --- a/app/public/views/periods/overview/dataExport.js +++ b/app/public/views/periods/overview/dataExport.js @@ -27,86 +27,87 @@ export const exportPreferences = { visible: 'visible', }; +const rowsPreferenceSelectId = 'rows-preference-selection'; +const columnsPreferenceSelectId = 'columns-preference-selection'; +const exportFormatSelectId = 'export-format-selection'; + +const exportData = async (dataModel, close) => { + const remoteData = dataModel.currentPageData; + + remoteData.match({ + NotAsked: () => {}, + Loading: () => {}, + Success: async () => { + await dataModel.createDataExport(remoteData.payload, selectedDataFormat()); + }, + Failure: () => {}, + }); + close(); +}; + +const handleRowsPreferenceSelection = () => { + const exportPreferenceSelection = document.getElementById(rowsPreferenceSelectId); + const selectedPreference = exportPreferenceSelection.options[exportPreferenceSelection.selectedIndex].value; + switch (selectedPreference) { + case exportPreferences.all: + /* */ + break; + case exportPreferences.currentPage: + /* */ + break; + case exportPreferences.selected: + /* */ + break; + case exportPreferences.notSelected: + /* */ + break; + case exportPreferences.visible: + /* */ + break; + default: + break; + } +}; + +const handleColumnsPreferenceSelection = () => { + const exportPreferenceSelection = document.getElementById(columnsPreferenceSelectId); + const selectedPreference = exportPreferenceSelection.options[exportPreferenceSelection.selectedIndex].value; + switch (selectedPreference) { + case exportPreferences.all: + /* */ + break; + case exportPreferences.selected: + /* */ + break; + case exportPreferences.notSelected: + /* */ + break; + case exportPreferences.visible: + /* */ + break; + default: + break; + } +}; + +const selectedDataFormat = () => { + const exportFormatSelection = document.getElementById(exportFormatSelectId); + const selectedFormat = exportFormatSelection.options[exportFormatSelection.selectedIndex].value; + switch (selectedFormat) { + case exportFormats.csv: + return selectedFormat; + case exportFormats.json: + return selectedFormat; + default: + return selectedFormat.csv; + } +}; + export default function dataExport(close, dataModel) { const pageName = dataModel.name; - const rowsPreferenceSelectId = 'rows-preference-selection'; - const columnsPreferenceSelectId = 'columns-preference-selection'; - const exportFormatSelectId = 'export-format-selection'; const title = h('h3.text-primary', `${pageName} export`); - const exportData = async () => { - const remoteData = dataModel.currentPageData; - - remoteData.match({ - NotAsked: () => {}, - Loading: () => {}, - Success: async () => { - await dataModel.createDataExport(remoteData.payload, selectedDataFormat()); - }, - Failure: () => {}, - }); - close(); - }; - - const handleRowsPreferenceSelection = () => { - const exportPreferenceSelection = document.getElementById(rowsPreferenceSelectId); - const selectedPreference = exportPreferenceSelection.options[exportPreferenceSelection.selectedIndex].value; - switch (selectedPreference) { - case exportPreferences.all: - /* */ - break; - case exportPreferences.currentPage: - /* */ - break; - case exportPreferences.selected: - /* */ - break; - case exportPreferences.notSelected: - /* */ - break; - case exportPreferences.visible: - /* */ - break; - default: - break; - } - }; - - const handleColumnsPreferenceSelection = () => { - const exportPreferenceSelection = document.getElementById(columnsPreferenceSelectId); - const selectedPreference = exportPreferenceSelection.options[exportPreferenceSelection.selectedIndex].value; - switch (selectedPreference) { - case exportPreferences.all: - /* */ - break; - case exportPreferences.selected: - /* */ - break; - case exportPreferences.notSelected: - /* */ - break; - case exportPreferences.visible: - /* */ - break; - default: - break; - } - }; - - const selectedDataFormat = () => { - const exportFormatSelection = document.getElementById(exportFormatSelectId); - const selectedFormat = exportFormatSelection.options[exportFormatSelection.selectedIndex].value; - switch (selectedFormat) { - case exportFormats.csv: - return selectedFormat; - case exportFormats.json: - return selectedFormat; - default: - return selectedFormat.csv; - } - }; - return h('.p-1rem', [ h('.flex.p-bottom-1rem.items-center', h('', title)), @@ -150,7 +151,7 @@ export default function dataExport(close, dataModel) { h('.flex-wrap.justify-center.items-center.p-1rem.p-bottom-0', h('button.btn.btn-primary', { - onclick: async () => await exportData(), + onclick: async () => await exportData(dataModel, close), }, 'Download file')), ]); } From 4277845e1bcb90626e0b99edcf38ac632c564048 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Fri, 25 Aug 2023 11:55:56 +0200 Subject: [PATCH 74/82] Move pageTitle to separate component --- app/public/components/common/pageTitle.js | 27 ++++++++ app/public/components/sidebar/sidebar.js | 2 +- app/public/components/table/title.js | 2 +- .../dataProcessing/dataProcessingUtils.js | 14 ----- app/public/views/periods/PeriodsModel.js | 2 +- test/public/components/index.js | 2 + test/public/components/pageTitle.test.js | 63 +++++++++++++++++++ test/public/utils/dataProcessing.test.js | 49 +-------------- 8 files changed, 96 insertions(+), 65 deletions(-) create mode 100644 app/public/components/common/pageTitle.js create mode 100644 test/public/components/pageTitle.test.js diff --git a/app/public/components/common/pageTitle.js b/app/public/components/common/pageTitle.js new file mode 100644 index 000000000..4da2a05d6 --- /dev/null +++ b/app/public/components/common/pageTitle.js @@ -0,0 +1,27 @@ +/** + * @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. + */ + +export const pageTitle = (page, pageNames) => { + switch (page) { + case pageNames.periods: return 'Periods'; + case pageNames.runsPerPeriod: return 'Runs per period'; + case pageNames.runsPerDataPass: return 'Runs per data pass'; + case pageNames.dataPasses: return 'Data passes per period'; + case pageNames.mc: return 'Monte Carlo'; + case pageNames.flags: return 'Quality flags'; + case pageNames.anchoragePerDatapass: return 'Anchorage per data pass'; + case pageNames.anchoredPerMC: return 'Anchored per MC'; + default: return page; + } +}; diff --git a/app/public/components/sidebar/sidebar.js b/app/public/components/sidebar/sidebar.js index 856c05497..632a2133c 100644 --- a/app/public/components/sidebar/sidebar.js +++ b/app/public/components/sidebar/sidebar.js @@ -16,7 +16,7 @@ import { h } from '/js/src/index.js'; import { RCT } from '../../config.js'; import sidebarItem from './sidebarItem.js'; import { modal, modalIds, showModal } from '../../views/modal/modal.js'; -import { pageTitle } from '../../utils/dataProcessing/dataProcessingUtils.js'; +import { pageTitle } from '../common/pageTitle.js'; const { pageNames } = RCT; /** diff --git a/app/public/components/table/title.js b/app/public/components/table/title.js index 6f0549764..271d3efe4 100644 --- a/app/public/components/table/title.js +++ b/app/public/components/table/title.js @@ -14,7 +14,7 @@ import { h } from '/js/src/index.js'; import { RCT } from '../../config.js'; -import { pageTitle } from '../../utils/dataProcessing/dataProcessingUtils.js'; +import { pageTitle } from '../common/pageTitle.js'; export default function title(page) { return h('h3.p-right-15.text-primary', pageTitle(page, RCT.pageNames)); diff --git a/app/public/utils/dataProcessing/dataProcessingUtils.js b/app/public/utils/dataProcessing/dataProcessingUtils.js index 709c36406..c7da73b23 100644 --- a/app/public/utils/dataProcessing/dataProcessingUtils.js +++ b/app/public/utils/dataProcessing/dataProcessingUtils.js @@ -56,17 +56,3 @@ export const getReadableFileSizeString = (fileSizeInBytes) => { return `${Math.max(result.fileSize, 0.1).toFixed(1)} ${byteUnits[result.index]}`; }; - -export const pageTitle = (page, pageNames) => { - switch (page) { - case pageNames.periods: return 'Periods'; - case pageNames.runsPerPeriod: return 'Runs per period'; - case pageNames.runsPerDataPass: return 'Runs per data pass'; - case pageNames.dataPasses: return 'Data passes per period'; - case pageNames.mc: return 'Monte Carlo'; - case pageNames.flags: return 'Quality flags'; - case pageNames.anchoragePerDatapass: return 'Anchorage per data pass'; - case pageNames.anchoredPerMC: return 'Anchored per MC'; - default: return page; - } -}; diff --git a/app/public/views/periods/PeriodsModel.js b/app/public/views/periods/PeriodsModel.js index 3d3293883..8365475c8 100644 --- a/app/public/views/periods/PeriodsModel.js +++ b/app/public/views/periods/PeriodsModel.js @@ -17,7 +17,7 @@ import { getRemoteDataSlice } from '../../utils/fetch/getRemoteDataSlice.js'; import { RCT } from '../../config.js'; import { createCSVExport, createJSONExport } from '../../utils/dataExport/export.js'; import { exportFormats } from './overview/dataExport.js'; -import { pageTitle } from '../../utils/dataProcessing/dataProcessingUtils.js'; +import { pageTitle } from '../../components/common/pageTitle.js'; /** * Model representing handlers for periods page diff --git a/test/public/components/index.js b/test/public/components/index.js index eb993bf21..617065b51 100644 --- a/test/public/components/index.js +++ b/test/public/components/index.js @@ -12,7 +12,9 @@ */ const itemsCounterSuite = require('./itemsCounter.test'); +const pageTitleSuite = require('./pageTitle.test'); module.exports = () => { describe('Items counter', itemsCounterSuite); + describe('Page title', pageTitleSuite); }; diff --git a/test/public/components/pageTitle.test.js b/test/public/components/pageTitle.test.js new file mode 100644 index 000000000..eba2ba77a --- /dev/null +++ b/test/public/components/pageTitle.test.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. + */ + +const req = require('esm')(module) +const { pageTitle } = req('../../../app/public/components/common/pageTitle'); +const assert = require('assert'); + +module.exports = () => { + describe('Page title', () => { + const pageNames = { + periods: 'periods', + dataPasses: 'dataPasses', + mc: 'mc', + anchoredPerMC: 'anchoredPerMC', + anchoragePerDatapass: 'anchoragePerDatapass', + runsPerPeriod: 'runsPerPeriod', + runsPerDataPass: 'runsPerDataPass', + flags: 'flags', + }; + + it('should return Periods page name' , () => { + assert.equal(pageTitle(pageNames.periods, pageNames), 'Periods'); + }); + + it('should return Data passes page name' , () => { + assert.equal(pageTitle(pageNames.dataPasses, pageNames), 'Data passes per period'); + }); + + it('should return Monte Carlo page name' , () => { + assert.equal(pageTitle(pageNames.mc, pageNames), 'Monte Carlo'); + }); + + it('should return Anchored per MC page name' , () => { + assert.equal(pageTitle(pageNames.anchoredPerMC, pageNames), 'Anchored per MC'); + }); + + it('should return Anchorage per data pass page name' , () => { + assert.equal(pageTitle(pageNames.anchoragePerDatapass, pageNames), 'Anchorage per data pass'); + }); + + it('should return Runs per period page name' , () => { + assert.equal(pageTitle(pageNames.runsPerPeriod, pageNames), 'Runs per period'); + }); + + it('should return Runs per data pass page name' , () => { + assert.equal(pageTitle(pageNames.runsPerDataPass, pageNames), 'Runs per data pass'); + }); + + it('should return Quality flags page name' , () => { + assert.equal(pageTitle(pageNames.flags, pageNames), 'Quality flags'); + }); + }); +}; diff --git a/test/public/utils/dataProcessing.test.js b/test/public/utils/dataProcessing.test.js index e704144bb..d1558c8ad 100644 --- a/test/public/utils/dataProcessing.test.js +++ b/test/public/utils/dataProcessing.test.js @@ -19,9 +19,7 @@ const { extractPeriodName, isDetectorField, shouldDisplayDetectorField, rowDisplayStyle, - capitalizeFirstLetter, - getReadableFileSizeString, - pageTitle } = req('../../../app/public/utils/dataProcessing/dataProcessingUtils'); + getReadableFileSizeString } = req('../../../app/public/utils/dataProcessing/dataProcessingUtils'); module.exports = () => { describe('Extract period name', () => { @@ -149,49 +147,4 @@ module.exports = () => { assert.equal(getReadableFileSizeString(fileSizeGB), '3.5 GB' ); }); }); - - describe('Page title', () => { - const pageNames = { - periods: 'periods', - dataPasses: 'dataPasses', - mc: 'mc', - anchoredPerMC: 'anchoredPerMC', - anchoragePerDatapass: 'anchoragePerDatapass', - runsPerPeriod: 'runsPerPeriod', - runsPerDataPass: 'runsPerDataPass', - flags: 'flags', - }; - - it('should return Periods page name' , () => { - assert.equal(pageTitle(pageNames.periods, pageNames), 'Periods'); - }); - - it('should return Data passes page name' , () => { - assert.equal(pageTitle(pageNames.dataPasses, pageNames), 'Data passes per period'); - }); - - it('should return Monte Carlo page name' , () => { - assert.equal(pageTitle(pageNames.mc, pageNames), 'Monte Carlo'); - }); - - it('should return Anchored per MC page name' , () => { - assert.equal(pageTitle(pageNames.anchoredPerMC, pageNames), 'Anchored per MC'); - }); - - it('should return Anchorage per data pass page name' , () => { - assert.equal(pageTitle(pageNames.anchoragePerDatapass, pageNames), 'Anchorage per data pass'); - }); - - it('should return Runs per period page name' , () => { - assert.equal(pageTitle(pageNames.runsPerPeriod, pageNames), 'Runs per period'); - }); - - it('should return Runs per data pass page name' , () => { - assert.equal(pageTitle(pageNames.runsPerDataPass, pageNames), 'Runs per data pass'); - }); - - it('should return Quality flags page name' , () => { - assert.equal(pageTitle(pageNames.flags, pageNames), 'Quality flags'); - }); - }); }; From 4a06657141d06ea6c75c3cebc616ee641156d542 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Fri, 25 Aug 2023 12:09:23 +0200 Subject: [PATCH 75/82] Chai export cleanup :broom: --- test/database/utilities/PGQueryBuilder.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/database/utilities/PGQueryBuilder.test.js b/test/database/utilities/PGQueryBuilder.test.js index fc1769dd7..04915eab8 100644 --- a/test/database/utilities/PGQueryBuilder.test.js +++ b/test/database/utilities/PGQueryBuilder.test.js @@ -13,9 +13,8 @@ const PGQueryBuilder = require('../../../app/lib/database/utilities/PGQueryBuilder'); const views = require('../../../app/lib/database/views'); -const expect = require('chai').expect; const { databaseService } = require('../../../app/lib/database/DatabaseService'); -const { assert } = require('chai'); +const { assert, expect } = require('chai'); module.exports = () => { From d25936a7415ecf9a4ded2244759bc149ce122fe3 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Fri, 25 Aug 2023 12:19:30 +0200 Subject: [PATCH 76/82] Tests cleanup :broom: --- test/lib/config/configProvider.test.js | 4 ++-- test/lib/utils/utils.test.js | 13 +++++++------ test/public/components/itemsCounter.test.js | 4 ++-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/test/lib/config/configProvider.test.js b/test/lib/config/configProvider.test.js index d8bb85650..d85627360 100644 --- a/test/lib/config/configProvider.test.js +++ b/test/lib/config/configProvider.test.js @@ -11,13 +11,13 @@ * or submit itself to any jurisdiction. */ -const assert = require('assert'); +const { assert } = require('chai'); const config = require('../../../app/lib/config/configProvider'); module.exports = () => { describe('Config Provider', () => { it('should return config file', () => { - assert(config !== null); + assert.isNotNull(config); }); it('should handle loading error properly', () => { diff --git a/test/lib/utils/utils.test.js b/test/lib/utils/utils.test.js index db652211c..31e9fb625 100644 --- a/test/lib/utils/utils.test.js +++ b/test/lib/utils/utils.test.js @@ -12,6 +12,7 @@ */ const assert = require('assert'); +const { expect } = require('chai'); const Utils = require('../../../app/lib/utils'); const ServicesDataCommons = require('../../../app/lib/alimonitor-services/ServicesDataCommons.js'); @@ -35,7 +36,7 @@ module.exports = () => { }); it('should return an empty object when told to suppress undefined values', () => { - assert(Object.keys(Utils.filterObject(objectSample, { noField: 'null' }, true)).length === 0); + expect(Object.keys(Utils.filterObject(objectSample, { noField: 'null' }, true))).to.lengthOf(0); }); }); @@ -47,11 +48,11 @@ module.exports = () => { }); it('should parse undefined values as null', () => { - assert(Utils.adjustValuesToSql(undefined) === 'null'); + assert.equal(Utils.adjustValuesToSql(undefined), 'null'); }); it('should return unquoted DEFAULT', () => { - assert(Utils.adjustValuesToSql('DEFAULT') === 'DEFAULT'); + assert.equal(Utils.adjustValuesToSql('DEFAULT'), 'DEFAULT'); }); }); @@ -76,12 +77,12 @@ module.exports = () => { const opts = { default: () => defaultVal }; it('should return correct value for each case', () => { - assert(Utils.switchCase(caseNames[0], cases, opts)() === caseNames[0]); - assert(Utils.switchCase(caseNames[1], cases, opts)() === caseNames[1]); + assert.equal(Utils.switchCase(caseNames[0], cases, opts)(), caseNames[0]); + assert.equal(Utils.switchCase(caseNames[1], cases, opts)(), caseNames[1]); }); it('should return default value when called with an unknown case', () => { - assert(Utils.switchCase(caseNames[2], cases, opts)() === defaultVal); + assert.equal(Utils.switchCase(caseNames[2], cases, opts)(), defaultVal); }); }); diff --git a/test/public/components/itemsCounter.test.js b/test/public/components/itemsCounter.test.js index d0840bc40..29c8e19f0 100644 --- a/test/public/components/itemsCounter.test.js +++ b/test/public/components/itemsCounter.test.js @@ -13,7 +13,7 @@ const req = require('esm')(module) const itemsCounter = req('../../../app/public/components/table/itemsCounter').default; -const assert = require('assert'); +const { assert } = require('chai'); module.exports = () => { describe('Items counter', () => { @@ -25,7 +25,7 @@ module.exports = () => { const expectedResult = '41-50 of 57'; it('should not return null', () => { - assert(itemsCounter(mockPaginationModel) !== null); + assert.isNotNull(itemsCounter(mockPaginationModel)); }); it('should count the items as expected', () => { From 9c581650df660fa793c43d3a76dd071837a22b04 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Fri, 25 Aug 2023 12:26:11 +0200 Subject: [PATCH 77/82] Tests cleanup :broom: --- test/public/utils/dataExport.test.js | 4 ++-- test/public/utils/dataProcessing.test.js | 3 ++- test/public/utils/obsoleteCsvExport.test.js | 8 ++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/test/public/utils/dataExport.test.js b/test/public/utils/dataExport.test.js index f056fb088..909ce1f6d 100644 --- a/test/public/utils/dataExport.test.js +++ b/test/public/utils/dataExport.test.js @@ -13,7 +13,7 @@ const req = require('esm')(module) const { replacer, prepareCSVContent } = req('../../../app/public/utils/dataExport/export'); -const assert = require('assert'); +const { assert } = require('chai'); module.exports = () => { describe('CSV Export', () => { @@ -64,7 +64,7 @@ module.exports = () => { `"LHC23zs",2023,"p-p",13596,[6797.4,6797.52,6797.64,6797.76,6797.88,6798,6798.24,6799.2]`; it('should not return null', () => { - assert(prepareCSVContent(rawData) !== null); + assert.isNotNull(prepareCSVContent(rawData)); }); it('should prepare CSV data content', () => { diff --git a/test/public/utils/dataProcessing.test.js b/test/public/utils/dataProcessing.test.js index d1558c8ad..0d08a56cf 100644 --- a/test/public/utils/dataProcessing.test.js +++ b/test/public/utils/dataProcessing.test.js @@ -13,6 +13,7 @@ const req = require('esm')(module) const assert = require('assert'); +const { expect } = require('chai'); const { extractPeriodName, getClosestDefinedEnergy, detectorName, @@ -69,7 +70,7 @@ module.exports = () => { }); it('should return null when provided field is not a detector field', () => { - assert(detectorName(nonDetectorFieldName) === null); + expect(detectorName(nonDetectorFieldName)).to.be.null; }) }); diff --git a/test/public/utils/obsoleteCsvExport.test.js b/test/public/utils/obsoleteCsvExport.test.js index a47ecd2f4..d158c1944 100644 --- a/test/public/utils/obsoleteCsvExport.test.js +++ b/test/public/utils/obsoleteCsvExport.test.js @@ -14,7 +14,7 @@ const req = require('esm')(module) const { preparedData } = req('../../../app/public/utils/obsoleteCsvExport'); const { preparedFile } = req('../../../app/public/utils/obsoleteCsvExport'); -const assert = require('assert'); +const { assert } = require('chai'); module.exports = () => { describe('CSV Export (Obsolete)', () => { @@ -44,12 +44,12 @@ module.exports = () => { describe('Check data preparation', () => { it('should not return null', () => { - assert(preparedData(dataSample) !== null); + assert.isNotNull(preparedData(dataSample)); }); it('should filter values properly', () => { - assert(preparedData(dataSample).indexOf('b field') === -1); - assert(preparedData(dataSample).indexOf(3) === -1); + assert.equal(preparedData(dataSample).indexOf('b field'), -1); + assert.equal(preparedData(dataSample).indexOf(3), -1); }); }); From 9cee9500c27a94dc3f4c2d701e1e4b419c222162 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Fri, 25 Aug 2023 14:00:22 +0200 Subject: [PATCH 78/82] Cleanup :broom: --- test/public/utils/dataExport.test.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/public/utils/dataExport.test.js b/test/public/utils/dataExport.test.js index 909ce1f6d..eadd01e4f 100644 --- a/test/public/utils/dataExport.test.js +++ b/test/public/utils/dataExport.test.js @@ -62,10 +62,6 @@ module.exports = () => { const preparedData = 'name,year,beamType,avgEnergy,distinctEnergies\r\n' + `"LHC23zt",2023,"p-p",13595,[6797.04,6797.52,6797.64,6798]\r\n` + `"LHC23zs",2023,"p-p",13596,[6797.4,6797.52,6797.64,6797.76,6797.88,6798,6798.24,6799.2]`; - - it('should not return null', () => { - assert.isNotNull(prepareCSVContent(rawData)); - }); it('should prepare CSV data content', () => { assert.equal(prepareCSVContent(rawData), preparedData); From d078ce29e2221dc835c3c9348453996a203e661a Mon Sep 17 00:00:00 2001 From: Ehevi Date: Mon, 28 Aug 2023 13:27:05 +0200 Subject: [PATCH 79/82] Address review comments --- app/public/utils/fetch/jsonFetch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/public/utils/fetch/jsonFetch.js b/app/public/utils/fetch/jsonFetch.js index b9bbcab7d..7c064069a 100644 --- a/app/public/utils/fetch/jsonFetch.js +++ b/app/public/utils/fetch/jsonFetch.js @@ -18,7 +18,7 @@ import { fetchClient } from '/js/src/index.js'; * * 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 data is returned, the json response should 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 From a83b26ec9f339845cfaf1be5ef5ae273bc22ed3f Mon Sep 17 00:00:00 2001 From: Ehevi Date: Mon, 28 Aug 2023 13:29:43 +0200 Subject: [PATCH 80/82] Address review comments --- app/public/components/table/pageSelector.js | 24 +++++++++++---------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/app/public/components/table/pageSelector.js b/app/public/components/table/pageSelector.js index 14af04f3b..061a03788 100644 --- a/app/public/components/table/pageSelector.js +++ b/app/public/components/table/pageSelector.js @@ -29,14 +29,16 @@ export default function pageSelector(currentPage, pagesCount, onPageChange) { const morePagesLeft = currentPage > 2; const morePagesRight = currentPage < pagesCount - 1; + const isFirstPage = currentPage > 1; + const isLastPage = currentPage < pagesCount; return h('.flex.m-right-0-3-rem', // Move to the first page - currentPage > 1 ? pageIconButton(1, h('.double-left-15-primary')) : '', - // Move one site back - currentPage > 1 ? pageIconButton(currentPage - 1, h('.back-15-primary')) : '', + isFirstPage ? pageIconButton(1, h('.double-left-15-primary')) : '', + // Move one page back + isFirstPage ? pageIconButton(currentPage - 1, h('.back-15-primary')) : '', - // Move to the middle of sites range [first, current] + // Move to the middle of pages range [first, current] morePagesLeft ? pageIconButton( Math.floor(currentPage / 2), @@ -44,11 +46,11 @@ export default function pageSelector(currentPage, pagesCount, onPageChange) { ) : '', - currentPage > 1 ? pageNumberButton(currentPage - 1) : '', + isFirstPage ? pageNumberButton(currentPage - 1) : '', pageNumberButton(currentPage), - currentPage < pagesCount ? pageNumberButton(currentPage + 1) : '', + isLastPage ? pageNumberButton(currentPage + 1) : '', - // Move to the middle of sites range [current, last] + // Move to the middle of pages range [current, last] morePagesRight ? pageIconButton( currentPage + Math.floor((pagesCount - currentPage) / 2), @@ -56,16 +58,16 @@ export default function pageSelector(currentPage, pagesCount, onPageChange) { ) : '', - // Move one site forward - currentPage < pagesCount + // Move one page forward + isLastPage ? pageIconButton( currentPage + 1, h('.forward-15-primary'), ) : '', - // Move to the last site - currentPage < pagesCount + // Move to the last page + isLastPage ? pageIconButton( pagesCount, h('.double-right-15-primary'), From da578114b032cd9dcaef02ce0149f7348e553a4f Mon Sep 17 00:00:00 2001 From: Ehevi Date: Mon, 28 Aug 2023 13:31:36 +0200 Subject: [PATCH 81/82] Address review comments --- .../views/periods/overview/dataExport.js | 47 ------------------- 1 file changed, 47 deletions(-) diff --git a/app/public/views/periods/overview/dataExport.js b/app/public/views/periods/overview/dataExport.js index a5c2707d6..0d7bc4e92 100644 --- a/app/public/views/periods/overview/dataExport.js +++ b/app/public/views/periods/overview/dataExport.js @@ -45,51 +45,6 @@ const exportData = async (dataModel, close) => { close(); }; -const handleRowsPreferenceSelection = () => { - const exportPreferenceSelection = document.getElementById(rowsPreferenceSelectId); - const selectedPreference = exportPreferenceSelection.options[exportPreferenceSelection.selectedIndex].value; - switch (selectedPreference) { - case exportPreferences.all: - /* */ - break; - case exportPreferences.currentPage: - /* */ - break; - case exportPreferences.selected: - /* */ - break; - case exportPreferences.notSelected: - /* */ - break; - case exportPreferences.visible: - /* */ - break; - default: - break; - } -}; - -const handleColumnsPreferenceSelection = () => { - const exportPreferenceSelection = document.getElementById(columnsPreferenceSelectId); - const selectedPreference = exportPreferenceSelection.options[exportPreferenceSelection.selectedIndex].value; - switch (selectedPreference) { - case exportPreferences.all: - /* */ - break; - case exportPreferences.selected: - /* */ - break; - case exportPreferences.notSelected: - /* */ - break; - case exportPreferences.visible: - /* */ - break; - default: - break; - } -}; - const selectedDataFormat = () => { const exportFormatSelection = document.getElementById(exportFormatSelectId); const selectedFormat = exportFormatSelection.options[exportFormatSelection.selectedIndex].value; @@ -117,7 +72,6 @@ export default function dataExport(close, dataModel) { h('select.select.color-theme', { id: rowsPreferenceSelectId, name: rowsPreferenceSelectId, - onchange: () => handleRowsPreferenceSelection(), }, [ h('option', { value: exportPreferences.all, disabled: true }, 'All'), h('option', { value: exportPreferences.currentPage }, 'Current page'), @@ -131,7 +85,6 @@ export default function dataExport(close, dataModel) { h('select.select.color-theme', { id: rowsPreferenceSelectId, name: rowsPreferenceSelectId, - onchange: () => handleColumnsPreferenceSelection(), }, [ h('option', { value: exportPreferences.all }, 'All'), h('option', { value: exportPreferences.selected, disabled: true }, 'Selected columns'), From 3f8bd4403f31ab129563d39c51869a0ac61afb95 Mon Sep 17 00:00:00 2001 From: Ehevi Date: Mon, 28 Aug 2023 13:33:19 +0200 Subject: [PATCH 82/82] Address review comments --- test/public/components/itemsCounter.test.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/public/components/itemsCounter.test.js b/test/public/components/itemsCounter.test.js index 29c8e19f0..31b04e8ba 100644 --- a/test/public/components/itemsCounter.test.js +++ b/test/public/components/itemsCounter.test.js @@ -24,10 +24,6 @@ module.exports = () => { } const expectedResult = '41-50 of 57'; - it('should not return null', () => { - assert.isNotNull(itemsCounter(mockPaginationModel)); - }); - it('should count the items as expected', () => { assert.equal(itemsCounter(mockPaginationModel), expectedResult); });