diff --git a/lib/public/views/Environments/EnvironmentModel.js b/lib/public/views/Environments/EnvironmentModel.js index c9f5cc12b8..ba4b1e86bf 100644 --- a/lib/public/views/Environments/EnvironmentModel.js +++ b/lib/public/views/Environments/EnvironmentModel.js @@ -23,12 +23,13 @@ import { EnvironmentDetailsModel } from './Details/EnvironmentDetailsModel.js'; export class EnvironmentModel extends Observable { /** * The constructor of the Overview model object + * @param {Model} model global model */ - constructor() { + constructor(model) { super(); // Sub-models - this._overviewModel = new EnvironmentOverviewModel(); + this._overviewModel = new EnvironmentOverviewModel(model); this._overviewModel.bubbleTo(this); this._detailsModel = new EnvironmentDetailsModel(); diff --git a/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js b/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js index f2f5c84bfe..1d701b4a27 100644 --- a/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js +++ b/lib/public/views/Environments/Overview/EnvironmentOverviewModel.js @@ -11,7 +11,10 @@ * or submit itself to any jurisdiction. */ +import { buildUrl } from '/js/src/index.js'; +import { FilteringModel } from '../../../components/Filters/common/FilteringModel.js'; import { OverviewPageModel } from '../../../models/OverviewModel.js'; +import { debounce } from '../../../utilities/debounce.js'; /** * Environment overview page model @@ -19,16 +22,31 @@ import { OverviewPageModel } from '../../../models/OverviewModel.js'; export class EnvironmentOverviewModel extends OverviewPageModel { /** * Constructor + * @param {Model} model global model */ - constructor() { + constructor(model) { super(); + + this._filteringModel = new FilteringModel({ + }); + + this._filteringModel.observe(() => this._applyFilters(true)); + this._filteringModel.visualChange$?.bubbleTo(this); + + this.reset(false); + const updateDebounceTime = () => { + this._debouncedLoad = debounce(this.load.bind(this), model.inputDebounceTime); + }; + + model.appConfiguration$.observe(() => updateDebounceTime()); + updateDebounceTime(); } /** * @inheritDoc */ getRootEndpoint() { - return '/api/environments'; + return buildUrl('/api/environments', { filter: this.filteringModel.normalized }); } /** @@ -39,4 +57,56 @@ export class EnvironmentOverviewModel extends OverviewPageModel { get environments() { return this.items; } + + /** + * Returns all filtering, sorting and pagination settings to their default values + * @param {boolean} [fetch = true] whether to refetch all data after filters have been reset + * @return {void} + */ + reset(fetch = true) { + super.reset(); + this.resetFiltering(fetch); + } + + /** + * Reset all filtering models + * @param {boolean} fetch Whether to refetch all data after filters have been reset + * @return {void} + */ + resetFiltering(fetch = true) { + this._filteringModel.reset(); + + if (fetch) { + this._applyFilters(true); + } + } + + /** + * Checks if any filter value has been modified from their default (empty) + * @return {Boolean} If any filter is active + */ + isAnyFilterActive() { + return this._filteringModel.isAnyFilterActive(); + } + + /** + * Return the filtering model + * + * @return {FilteringModel} the filtering model + */ + get filteringModel() { + return this._filteringModel; + } + + /** + * Apply the current filtering and update the remote data list + * + * @param {boolean} now if true, filtering will be applied now without debouncing + * + * @return {void} + */ + _applyFilters(now = false) { + this._pagination.currentPage = 1; + now ? this.load() : this._debouncedLoad(true); + } } diff --git a/lib/public/views/Environments/Overview/environmentOverviewComponent.js b/lib/public/views/Environments/Overview/environmentOverviewComponent.js index 85c55d4155..7cc60ecd22 100644 --- a/lib/public/views/Environments/Overview/environmentOverviewComponent.js +++ b/lib/public/views/Environments/Overview/environmentOverviewComponent.js @@ -16,6 +16,7 @@ import { table } from '../../../components/common/table/table.js'; import { environmentsActiveColumns } from '../ActiveColumns/environmentsActiveColumns.js'; import { estimateDisplayableRowsCount } from '../../../utilities/estimateDisplayableRowsCount.js'; import { paginationComponent } from '../../../components/Pagination/paginationComponent.js'; +import { filtersPanelPopover } from '../../../components/Filters/common/filtersPanelPopover.js'; const TABLEROW_HEIGHT = 58; // Estimate of the navbar and pagination elements height total; Needs to be updated in case of changes; @@ -34,11 +35,15 @@ export const environmentOverviewComponent = (envsOverviewModel) => { PAGE_USED_HEIGHT, )); - return [ + return h('', [ + h( + '.flex-row.header-container.g2.pv2', + filtersPanelPopover(envsOverviewModel, environmentsActiveColumns), + ), h('.w-100.flex-column', [ h('.header-container.pv2'), table(environments, environmentsActiveColumns, { classes: 'table-sm' }), paginationComponent(pagination), ]), - ]; + ]); }; diff --git a/test/public/envs/overview.test.js b/test/public/envs/overview.test.js index 2a001e0960..ae673ef4e0 100644 --- a/test/public/envs/overview.test.js +++ b/test/public/envs/overview.test.js @@ -25,6 +25,7 @@ const { waitForTableLength, getPopoverSelector, goToPage, + openFilteringPanel, } = require('../defaults.js'); const dateAndTime = require('date-and-time'); const { resetDatabaseContent } = require('../../utilities/resetDatabaseContent.js'); @@ -273,4 +274,20 @@ module.exports = () => { }); expect(loadCallCount).to.equal(0); }); + + it('should successfully open the filtering panel', async () => { + // Get the popover key from the filter button's parent + const filterButton = await page.waitForSelector('#openFilterToggle'); + const popoverKey = await filterButton.evaluate((button) => { + return button.parentElement.getAttribute('data-popover-key'); + }); + const filterPanelSelector = `.popover[data-popover-key="${popoverKey}"]`; + + // Initially the filtering panel should be closed + await page.waitForSelector(filterPanelSelector, { hidden: true }); + + // Open the filtering panel + await openFilteringPanel(page); + await page.waitForSelector(filterPanelSelector, { visible: true }); + }); };