diff --git a/lib/public/components/Filters/LogsFilter/author/AuthorFilterModel.js b/lib/public/components/Filters/LogsFilter/author/AuthorFilterModel.js index 1b7a133916..6e76b7b3e1 100644 --- a/lib/public/components/Filters/LogsFilter/author/AuthorFilterModel.js +++ b/lib/public/components/Filters/LogsFilter/author/AuthorFilterModel.js @@ -11,12 +11,12 @@ * or submit itself to any jurisdiction. */ -import { FilterInputModel } from '../../common/filters/FilterInputModel.js'; +import { RawTextFilterModel } from '../../common/filters/RawTextFilterModel.js'; /** * Model to handle the state of the Author Filter */ -export class AuthorFilterModel extends FilterInputModel { +export class AuthorFilterModel extends RawTextFilterModel { /** * Constructor * @@ -32,7 +32,7 @@ export class AuthorFilterModel extends FilterInputModel { * @return {boolean} true if '!Anonymous' is included in the raw filter string, false otherwise. */ isAnonymousExcluded() { - return this._raw.includes('!Anonymous'); + return this._value.includes('!Anonymous'); } /** @@ -42,28 +42,13 @@ export class AuthorFilterModel extends FilterInputModel { */ toggleAnonymousFilter() { if (this.isAnonymousExcluded()) { - this._raw = this._raw.split(',') + this._value = this._value.split(',') .filter((author) => author.trim() !== '!Anonymous') .join(','); } else { - this._raw += super.isEmpty ? '!Anonymous' : ', !Anonymous'; + this._value += super.isEmpty ? '!Anonymous' : ', !Anonymous'; } - this._value = this.valueFromRaw(this._raw); - this.notify(); - } - - /** - * Reset the filter to its default value and notify the observers. - * - * @return {void} - */ - clear() { - if (this.isEmpty) { - return; - } - - super.reset(); this.notify(); } } diff --git a/lib/public/components/Filters/LogsFilter/author/authorFilter.js b/lib/public/components/Filters/LogsFilter/author/authorFilter.js index d5fe5a7a45..7cfc2b7d7e 100644 --- a/lib/public/components/Filters/LogsFilter/author/authorFilter.js +++ b/lib/public/components/Filters/LogsFilter/author/authorFilter.js @@ -14,19 +14,7 @@ import { h } from '/js/src/index.js'; import { iconX } from '/js/src/icons.js'; import { switchInput } from '../../../common/form/switchInput.js'; - -/** - * Returns a text input field that can be used to filter logs by author - * - * @param {AuthorFilterModel} authorFilterModel The author filter model object - * @returns {Component} A text box that allows the user to enter an author substring to match against all logs - */ -const authorFilterTextInput = (authorFilterModel) => h('input.w-40', { - type: 'text', - id: 'authorFilterText', - value: authorFilterModel.raw, - oninput: (e) => authorFilterModel.update(e.target.value), -}); +import { rawTextFilter } from '../../common/filters/rawTextFilter.js'; /** * Returns a button that can be used to reset the author filter. @@ -36,7 +24,7 @@ const authorFilterTextInput = (authorFilterModel) => h('input.w-40', { */ const resetAuthorFilterButton = (authorFilterModel) => h( '.btn.btn-pill.f7', - { disabled: authorFilterModel.isEmpty, onclick: () => authorFilterModel.clear() }, + { disabled: authorFilterModel.isEmpty, onclick: () => authorFilterModel.reset() }, iconX(), ); @@ -55,11 +43,16 @@ export const excludeAnonymousLogAuthorToggle = (authorFilterModel) => switchInpu /** * Returns a authorFilter component with text input, reset button, and anonymous exclusion button. * - * @param {LogModel} logModel the log model object - * @returns {Component} the author filter component + * @param {LogsOverviewModel} logsOverviewModel the log overview model + * @param {FilteringModel} logsOverviewModel.filteringModel the runs overview model + * @return {Component} the filter component */ -export const authorFilter = ({ authorFilter }) => h('.flex-row.items-center.g3', [ - authorFilterTextInput(authorFilter), - resetAuthorFilterButton(authorFilter), - excludeAnonymousLogAuthorToggle(authorFilter), +export const authorFilter = ({ filteringModel }) => h('.flex-row.items-center.g3', [ + rawTextFilter(filteringModel.get('authorFilter'), { + classes: ['w-40'], + id: 'authorFilterText', + value: filteringModel.get('authorFilter').raw, + }), + resetAuthorFilterButton(filteringModel.get('authorFilter')), + excludeAnonymousLogAuthorToggle(filteringModel.get('authorFilter')), ]); diff --git a/lib/public/components/Filters/common/filters/FilterInputModel.js b/lib/public/components/Filters/common/filters/ParsedInputFilterModel.js similarity index 63% rename from lib/public/components/Filters/common/filters/FilterInputModel.js rename to lib/public/components/Filters/common/filters/ParsedInputFilterModel.js index 8860edf61d..7cdd6dc8ef 100644 --- a/lib/public/components/Filters/common/filters/FilterInputModel.js +++ b/lib/public/components/Filters/common/filters/ParsedInputFilterModel.js @@ -10,48 +10,44 @@ * 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'; + +import { FilterModel } from '../FilterModel.js'; /** - * Model for a generic filter input + * Model that parses raw intput into a value */ -export class FilterInputModel extends Observable { +export class ParsedInputFilterModel extends FilterModel { /** * Constructor + * + * @param {callback} parse function called to parse a value from a raw value */ - constructor() { + constructor(parse) { super(); + this._parse = parse; this._value = null; - this._raw = ''; - - this._visualChange$ = new Observable(); } /** * Define the current value of the filter * * @param {string} raw the raw value of the filter + * @override * @return {void} */ update(raw) { - const previousValues = this.value; - - this._value = this.valueFromRaw(raw); this._raw = raw; + const value = this._parse(raw); - if (this.areValuesEquals(this.value, previousValues)) { - // Only raw value changed - this._visualChange$.notify(); - } else { + if (!this.areValuesEquals(this._value, value)) { + this._value = value; this.notify(); } } /** - * Reset the filter to its default value - * - * @return {void} + * @inheritdoc */ reset() { this._value = null; @@ -86,23 +82,10 @@ export class FilterInputModel extends Observable { } /** - * Returns the observable notified any time there is a visual change which has no impact on the actual filter value - * - * @return {Observable} the observable - */ - get visualChange$() { - return this._visualChange$; - } - - /** - * Returns the processed value from raw input - * - * @param {string} raw the raw input value - * @return {*} the processed value - * @protected + * @inheritdoc */ - valueFromRaw(raw) { - return raw.trim(); + get normalized() { + return this.value; } /** diff --git a/lib/public/components/Filters/common/filters/textFilter.js b/lib/public/components/Filters/common/filters/textFilter.js index 6b288d54ac..529f9e7692 100644 --- a/lib/public/components/Filters/common/filters/textFilter.js +++ b/lib/public/components/Filters/common/filters/textFilter.js @@ -16,7 +16,7 @@ import { h } from '/js/src/index.js'; /** * Returns a text filter component * - * @param {FilterInputModel|TextTokensFilterModel} filterInputModel the model of the text filter + * @param {ParsedInputFilterModel|TextTokensFilterModel} filterInputModel the model of the text filter * @param {Object} attributes the additional attributes to pass to the component, such as id and classes * @return {Component} the filter component */ diff --git a/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js b/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js index c43b04b917..21402ca680 100644 --- a/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js +++ b/lib/public/views/Logs/ActiveColumns/logsActiveColumns.js @@ -15,19 +15,16 @@ import { h } from '/js/src/index.js'; import { iconCommentSquare, iconPaperclip } from '/js/src/icons.js'; import { authorFilter } from '../../../components/Filters/LogsFilter/author/authorFilter.js'; -import createdFilter from '../../../components/Filters/LogsFilter/created.js'; -import runsFilter from '../../../components/Filters/LogsFilter/runs.js'; import { formatTimestamp } from '../../../utilities/formatting/formatTimestamp.js'; import { frontLink } from '../../../components/common/navigation/frontLink.js'; import { frontLinks } from '../../../components/common/navigation/frontLinks.js'; import { tagFilter } from '../../../components/Filters/common/filters/tagFilter.js'; import { formatRunsList } from '../../Runs/format/formatRunsList.js'; import { profiles } from '../../../components/common/table/profiles.js'; -import { textFilter } from '../../../components/Filters/common/filters/textFilter.js'; -import { environmentFilter } from '../../../components/Filters/LogsFilter/environments.js'; import { formatLhcFillsList } from '../../LhcFills/format/formatLhcFillsList.js'; -import { lhcFillsFilter } from '../../../components/Filters/LogsFilter/lhcFill.js'; import { formatTagsList } from '../../Tags/format/formatTagsList.js'; +import { rawTextFilter } from '../../../components/Filters/common/filters/rawTextFilter.js'; +import { timeRangeFilter } from '../../../components/Filters/common/filters/timeRangeFilter.js'; /** * A method to display a small and simple number/icon collection as a column @@ -71,8 +68,16 @@ export const logsActiveColumns = { visible: true, sortable: true, size: 'w-30', - filter: ({ titleFilter }) => textFilter( - titleFilter, + + /** + * Title filter component + * + * @param {LogsOverviewModel} logOverviewModel the logs overview model + * @param {FilteringModel} logOverviewModel.filteringModel filtering model + * @return {Component} the filter component + */ + filter: ({ filteringModel }) => rawTextFilter( + filteringModel.get('titleFilter'), { id: 'titleFilterText', class: 'w-75 mt1', @@ -92,11 +97,19 @@ export const logsActiveColumns = { name: 'Content', visible: false, size: 'w-10', - filter: ({ contentFilter }) => textFilter( - contentFilter, + + /** + * Content filter component + * + * @param {LogsOverviewModel} logOverviewModel the logs overview model + * @param {FilteringModel} logOverviewModel.filteringModel filtering model + * @return {Component} the filter component + */ + filter: ({ filteringModel }) => rawTextFilter( + filteringModel.get('contentFilter'), { id: 'contentFilterText', - class: 'w-75 mt1', + classes: ['w-75', 'mt1'], }, ), }, @@ -115,7 +128,15 @@ export const logsActiveColumns = { sortable: true, size: 'w-10', format: (timestamp) => formatTimestamp(timestamp, false), - filter: createdFilter, + + /** + * Created filter component + * + * @param {LogsOverviewModel} logOverviewModel the logs overview model + * @param {FilteringModel} logOverviewModel.filteringModel filtering model + * @return {Component} the filter component + */ + filter: ({ filteringModel }) => timeRangeFilter(filteringModel.get('created')), profiles: { embeded: { format: (timestamp) => formatTimestamp(timestamp), @@ -137,10 +158,12 @@ export const logsActiveColumns = { /** * Tag filter component - * @param {LogsOverviewModel} logsModel the log model + * + * @param {LogsOverviewModel} logOverviewModel the logs overview model + * @param {FilteringModel} logOverviewModel.filteringModel filtering model * @return {Component} the filter component */ - filter: (logsModel) => tagFilter(logsModel.listingTagsFilterModel), + filter: ({ filteringModel }) => tagFilter(filteringModel.get('tags')), balloon: true, profiles: [profiles.none, 'embeded'], }, @@ -150,7 +173,22 @@ export const logsActiveColumns = { sortable: true, size: 'w-15', format: formatRunsList, - filter: runsFilter, + + /** + * Runs filter component + * + * @param {LogsOverviewModel} logOverviewModel the logs overview model + * @param {FilteringModel} logOverviewModel.filteringModel filtering model + * @return {Component} the filter component + */ + filter: ({ filteringModel }) => rawTextFilter( + filteringModel.get('run'), + { + id: 'runsFilterText', + classes: ['w-75', 'mt1'], + placeholder: 'e.g. 553203, 553221, ...', + }, + ), balloon: true, profiles: [profiles.none, 'embeded'], }, @@ -167,7 +205,22 @@ export const logsActiveColumns = { parameters: { environmentId: id }, }), ), - filter: environmentFilter, + + /** + * Environment filter component + * + * @param {LogsOverviewModel} logOverviewModel the logs overview model + * @param {FilteringModel} logOverviewModel.filteringModel filtering model + * @return {Component} the filter component + */ + filter: ({ filteringModel }) => rawTextFilter( + filteringModel.get('environments'), + { + id: 'environmentFilterText', + classes: ['w-75', 'mt1'], + placeholder: 'e.g. Dxi029djX, TDI59So3d...', + }, + ), balloon: true, profiles: [profiles.none, 'embeded'], }, @@ -177,7 +230,22 @@ export const logsActiveColumns = { sortable: false, size: 'w-10', format: formatLhcFillsList, - filter: lhcFillsFilter, + + /** + * LhcFills filter component + * + * @param {LogsOverviewModel} logOverviewModel the logs overview model + * @param {FilteringModel} logOverviewModel.filteringModel filtering model + * @return {Component} the filter component + */ + filter: ({ filteringModel }) => rawTextFilter( + filteringModel.get('lhcFills'), + { + id: 'lhcFillsFilterText', + classes: ['w-75', 'mt1'], + placeholder: 'e.g. 11392, 11383, 7625', + }, + ), balloon: true, profiles: [profiles.none, 'embeded'], }, diff --git a/lib/public/views/Logs/Overview/LogsOverviewModel.js b/lib/public/views/Logs/Overview/LogsOverviewModel.js index cce376438b..23946cec78 100644 --- a/lib/public/views/Logs/Overview/LogsOverviewModel.js +++ b/lib/public/views/Logs/Overview/LogsOverviewModel.js @@ -15,11 +15,13 @@ import { buildUrl, Observable, RemoteData } from '/js/src/index.js'; import { TagFilterModel } from '../../../components/Filters/common/TagFilterModel.js'; import { SortModel } from '../../../components/common/table/SortModel.js'; import { debounce } from '../../../utilities/debounce.js'; -import { FilterInputModel } from '../../../components/Filters/common/filters/FilterInputModel.js'; import { AuthorFilterModel } from '../../../components/Filters/LogsFilter/author/AuthorFilterModel.js'; import { PaginationModel } from '../../../components/Pagination/PaginationModel.js'; import { getRemoteDataSlice } from '../../../utilities/fetch/getRemoteDataSlice.js'; import { tagsProvider } from '../../../services/tag/tagsProvider.js'; +import { FilteringModel } from '../../../components/Filters/common/FilteringModel.js'; +import { RawTextFilterModel } from '../../../components/Filters/common/filters/RawTextFilterModel.js'; +import { TimeRangeInputModel } from '../../../components/Filters/common/filters/TimeRangeInputModel.js'; /** * Model representing handlers for log entries page @@ -36,13 +38,21 @@ export class LogsOverviewModel extends Observable { constructor(model, excludeAnonymous = false) { super(); - this.model = model; + this._filteringModel = new FilteringModel({ + authorFilter: new AuthorFilterModel(), + titleFilter: new RawTextFilterModel(), + contentFilter: new RawTextFilterModel(), + tags: new TagFilterModel(tagsProvider.items$), + run: new RawTextFilterModel(), + environments: new RawTextFilterModel(), + lhcFills: new RawTextFilterModel(), + created: new TimeRangeInputModel(), + }); - // Sub-models - this._listingTagsFilterModel = new TagFilterModel(tagsProvider.items$); - this._listingTagsFilterModel.observe(() => this._applyFilters()); - this._listingTagsFilterModel.visualChange$.bubbleTo(this); + this._filteringModel.observe(() => this._applyFilters()); + this._filteringModel.visualChange$.bubbleTo(this); + // Sub-models this._overviewSortModel = new SortModel(); this._overviewSortModel.observe(() => this._applyFilters(true)); this._overviewSortModel.visualChange$.bubbleTo(this); @@ -51,16 +61,6 @@ export class LogsOverviewModel extends Observable { this._pagination.observe(() => this.fetchLogs()); this._pagination.itemsPerPageSelector$.observe(() => this.notify()); - // Filtering models - this._authorFilter = new AuthorFilterModel(); - this._registerFilter(this._authorFilter); - - this._titleFilter = new FilterInputModel(); - this._registerFilter(this._titleFilter); - - this._contentFilter = new FilterInputModel(); - this._registerFilter(this._contentFilter); - this._logs = RemoteData.NotAsked(); const updateDebounceTime = () => { @@ -69,7 +69,7 @@ export class LogsOverviewModel extends Observable { model.appConfiguration$.observe(() => updateDebounceTime()); updateDebounceTime(); - excludeAnonymous && this._authorFilter.update('!Anonymous'); + excludeAnonymous && this._filteringModel.get('authorFilter').update('!Anonymous'); this.reset(false); } @@ -80,6 +80,8 @@ export class LogsOverviewModel extends Observable { */ async fetchLogs() { const keepExisting = this._pagination.currentPage > 1 && this._pagination.isInfiniteScrollEnabled; + const sortOn = this._overviewSortModel.appliedOn; + const sortDirection = this._overviewSortModel.appliedDirection; if (!keepExisting) { this._logs = RemoteData.loading(); @@ -87,6 +89,9 @@ export class LogsOverviewModel extends Observable { } const params = { + ...sortOn && sortDirection && { + [`sort[${sortOn}]`]: sortDirection, + }, ...this._getFilterQueryParams(), 'page[offset]': this._pagination.firstItemOffset, 'page[limit]': this._pagination.itemsPerPage, @@ -121,26 +126,11 @@ export class LogsOverviewModel extends Observable { * @return {undefined} */ reset(fetch = true) { - this.titleFilter.reset(); - this.contentFilter.reset(); - this.authorFilter.reset(); - - this.createdFilterFrom = ''; - this.createdFilterTo = ''; - - this.listingTagsFilterModel.reset(); + this._filteringModel.reset(); this.runFilterOperation = 'AND'; - this.runFilterValues = []; - this._runFilterRawValue = ''; - this.environmentFilterOperation = 'AND'; - this.environmentFilterValues = []; - this._environmentFilterRawValue = ''; - this.lhcFillFilterOperation = 'AND'; - this.lhcFillFilterValues = []; - this._lhcFillFilterRawValue = ''; this._pagination.reset(); @@ -154,150 +144,16 @@ export class LogsOverviewModel extends Observable { * @returns {boolean} If any filter is active */ isAnyFilterActive() { - return ( - !this._titleFilter.isEmpty - || !this._contentFilter.isEmpty - || !this._authorFilter.isEmpty - || this.createdFilterFrom !== '' - || this.createdFilterTo !== '' - || !this.listingTagsFilterModel.isEmpty - || this.runFilterValues.length !== 0 - || this.environmentFilterValues.length !== 0 - || this.lhcFillFilterValues.length !== 0 - ); - } - - /** - * Returns the current title substring filter - * @returns {string} The current title substring filter - */ - getRunsFilterRaw() { - return this._runFilterRawValue; - } - - /** - * Add a run to the filter - * @param {string} rawRuns The runs to be added to the filter criteria - * @returns {undefined} - */ - setRunsFilter(rawRuns) { - this._runFilterRawValue = rawRuns; - const runs = []; - const valuesRegex = /([0-9]+),?/g; - - let match = valuesRegex.exec(rawRuns); - while (match) { - runs.push(parseInt(match[1], 10)); - match = valuesRegex.exec(rawRuns); - } - - // Allow empty runs only if raw runs is an empty string - if (runs.length > 0 || rawRuns.length === 0) { - this.runFilterValues = runs; - this._applyFilters(); - } - } - - /** - * Returns the raw current environment filter - * @returns {string} the raw current environment filter - */ - getEnvFilterRaw() { - return this._environmentFilterRawValue; - } - - /** - * Returns the current environment filter - * @returns {string[]} The current environment filter - */ - getEnvFilter() { - return this.environmentFilterValues; - } - - /** - * Sets the environment filter - * @param {string} rawEnvironments The environments to apply to the filter - * @returns {undefined} - */ - setEnvFilter(rawEnvironments) { - this._environmentFilterRawValue = rawEnvironments; - const envs = rawEnvironments - .split(/[ ,]+/) - .filter(Boolean) - .map((id) => id.trim()); - - if (envs.length > 0 || rawEnvironments.length === 0) { - this.environmentFilterValues = envs; - this._applyFilters(); - } - } - - /** - * Returns the current title substring filter - * @returns {string} The current title substring filter - */ - getLhcFillsFilterRaw() { - return this._lhcFillFilterRawValue; - } - - /** - * Add a lhcFill to the filter - * @param {string} rawLhcFills The LHC fills to be added to the filter criteria - * @returns {void} - */ - setLhcFillsFilter(rawLhcFills) { - this._lhcFillFilterRawValue = rawLhcFills; - - // Split the lhc fills string by comma or whitespace, remove falsy values like empty strings, and convert to int - const lhcFills = rawLhcFills - .split(/[ ,]+/) - .filter(Boolean) - .map((fillNumberStr) => parseInt(fillNumberStr.trim(), 10)); - - // Allow empty lhcFills only if raw lhcFills is an empty string - if (lhcFills.length > 0 || rawLhcFills.length === 0) { - this.lhcFillFilterValues = lhcFills; - this._applyFilters(); - } + return this._filteringModel.isAnyFilterActive(); } /** - * Returns the current minimum creation datetime - * @returns {Integer} The current minimum creation datetime - */ - getCreatedFilterFrom() { - return this.createdFilterFrom; - } - - /** - * Returns the current maximum creation datetime - * @returns {Integer} The current maximum creation datetime - */ - getCreatedFilterTo() { - return this.createdFilterTo; - } - - /** - * Set a datetime for the creation datetime filter - * @param {string} key The filter value to apply the datetime to - * @param {Object} date The datetime to be applied to the creation datetime filter - * @param {boolean} valid Whether the inserted date passes validity check - * @returns {undefined} - */ - setCreatedFilter(key, date, valid) { - if (valid) { - this[`createdFilter${key}`] = date; - this._applyFilters(); - } - } - - /** - * Return the model handling the filtering on tags + * Return the model managing all filters * - * @return {TagFilterModel} the filtering model + * @return {FilteringModel} the filtering model */ - get listingTagsFilterModel() { - return this._listingTagsFilterModel; + get filteringModel() { + return this._filteringModel; } /** @@ -309,32 +165,6 @@ export class LogsOverviewModel extends Observable { return this._overviewSortModel; } - /** - * Returns the filter model for author filter - * - * @return {FilterInputModel} the filter model - */ - get authorFilter() { - return this._authorFilter; - } - - /** - * Returns the filter model for title filter - * - * @return {FilterInputModel} the filter model - */ - get titleFilter() { - return this._titleFilter; - } - - /** - * Returns the model for body filter - * @return {FilterInputModel} the filter model - */ - get contentFilter() { - return this._contentFilter; - } - /** * Returns the pagination model * @@ -356,17 +186,6 @@ export class LogsOverviewModel extends Observable { now ? this.fetchLogs() : this._debouncedFetchAllLogs(); } - /** - * Register a new filter model - * @param {FilterInputModel} filter the filter to register - * @return {void} - * @private - */ - _registerFilter(filter) { - filter.visualChange$.bubbleTo(this); - filter.observe(() => this._applyFilters()); - } - /** * Returns the list of URL params corresponding to the currently applied filter * @@ -375,46 +194,45 @@ export class LogsOverviewModel extends Observable { * @private */ _getFilterQueryParams() { - const sortOn = this._overviewSortModel.appliedOn; - const sortDirection = this._overviewSortModel.appliedDirection; + const titleFilter = this._filteringModel.get('titleFilter'); + const contentFilter = this._filteringModel.get('contentFilter'); + const authorFilter = this._filteringModel.get('authorFilter'); + const tags = this._filteringModel.get('tags'); + const run = this._filteringModel.get('run'); + const environments = this._filteringModel.get('environments'); + const lhcFills = this._filteringModel.get('lhcFills'); + const created = this._filteringModel.get('created'); return { - ...!this._titleFilter.isEmpty && { - 'filter[title]': this._titleFilter.value, - }, - ...!this._contentFilter.isEmpty && { - 'filter[content]': this._contentFilter.value, + ...!titleFilter.isEmpty && { + 'filter[title]': titleFilter.normalized, }, - ...!this._authorFilter.isEmpty && { - 'filter[author]': this._authorFilter.value, + ...!contentFilter.isEmpty && { + 'filter[content]': contentFilter.normalized, }, - ...this.createdFilterFrom && { - 'filter[created][from]': - new Date(`${this.createdFilterFrom.replace(/\//g, '-')}T00:00:00.000`).getTime(), + ...!authorFilter.isEmpty && { + 'filter[author]': authorFilter.normalized, }, - ...this.createdFilterTo && { - 'filter[created][to]': - new Date(`${this.createdFilterTo.replace(/\//g, '-')}T23:59:59.999`).getTime(), + ...!created.isEmpty && { + 'filter[created][from]': created.normalized.from, + 'filter[created][to]': created.normalized.to, }, - ...!this.listingTagsFilterModel.isEmpty && { - 'filter[tags][values]': this.listingTagsFilterModel.selected.join(), - 'filter[tags][operation]': this.listingTagsFilterModel.combinationOperator, + ...!tags.isEmpty && { + 'filter[tags][values]': tags.selected.join(), + 'filter[tags][operation]': tags.combinationOperator, }, - ...this.runFilterValues.length > 0 && { - 'filter[run][values]': this.runFilterValues.join(), + ...!run.isEmpty && { + 'filter[run][values]': run.normalized, 'filter[run][operation]': this.runFilterOperation.toLowerCase(), }, - ...this.environmentFilterValues.length > 0 && { - 'filter[environments][values]': this.environmentFilterValues, + ...!environments.isEmpty && { + 'filter[environments][values]': environments.normalized, 'filter[environments][operation]': this.environmentFilterOperation.toLowerCase(), }, - ...this.lhcFillFilterValues.length > 0 && { - 'filter[lhcFills][values]': this.lhcFillFilterValues.join(), + ...!lhcFills.isEmpty && { + 'filter[lhcFills][values]': lhcFills.normalized, 'filter[lhcFills][operation]': this.lhcFillFilterOperation.toLowerCase(), }, - ...sortOn && sortDirection && { - [`sort[${sortOn}]`]: sortDirection, - }, }; } } diff --git a/lib/public/views/Logs/Overview/index.js b/lib/public/views/Logs/Overview/index.js index 012f6e7bfe..ed5c7a860c 100644 --- a/lib/public/views/Logs/Overview/index.js +++ b/lib/public/views/Logs/Overview/index.js @@ -39,7 +39,7 @@ const logOverviewScreen = ({ logs: { overviewModel: logsOverviewModel } }) => { h('#main-action-bar.flex-row.justify-between.header-container.pv2', [ h('.flex-row.g3', [ filtersPanelPopover(logsOverviewModel, logsActiveColumns), - excludeAnonymousLogAuthorToggle(logsOverviewModel.authorFilter), + excludeAnonymousLogAuthorToggle(logsOverviewModel.filteringModel.get('authorFilter')), ]), actionButtons(), ]), diff --git a/test/public/logs/overview.test.js b/test/public/logs/overview.test.js index 39119d7ef1..357584425c 100644 --- a/test/public/logs/overview.test.js +++ b/test/public/logs/overview.test.js @@ -34,6 +34,9 @@ const { waitForEmptyTable, waitForTableTotalRowsCountToEqual, waitForTableFirstRowIndexToEqual, + openFilteringPanel, + resetFilters, + getPeriodInputsSelectors, } = require('../defaults.js'); const { resetDatabaseContent } = require('../../utilities/resetDatabaseContent.js'); @@ -91,44 +94,41 @@ module.exports = () => { it('can filter by log title', async () => { await waitForTableLength(page, 10); - await pressElement(page, '#openFilterToggle'); - await page.waitForSelector('#titleFilterText'); - - await fillInput(page, '#titleFilterText', 'first'); + await openFilteringPanel(page) + await fillInput(page, '#titleFilterText', 'first', ['change']); await waitForTableLength(page, 1); - await fillInput(page, '#titleFilterText', 'bogusbogusbogus'); + await fillInput(page, '#titleFilterText', 'bogusbogusbogus', ['change']); await waitForEmptyTable(page); - - await pressElement(page, '#reset-filters'); + await resetFilters(page); }); it('should successfully provide an input to filter on log content', async () => { await waitForTableLength(page, 10); - await fillInput(page, '#contentFilterText', 'particle'); + await fillInput(page, '#contentFilterText', 'particle', ['change']); await waitForTableLength(page, 2); - await fillInput(page, '#titleFilterText', 'this-content-do-not-exists-anywhere'); + await fillInput(page, '#titleFilterText', 'this-content-do-not-exists-anywhere', ['change']); await waitForEmptyTable(page); - await pressElement(page, '#reset-filters'); + await resetFilters(page); }); it('can filter by log author', async () => { await waitForTableLength(page, 10); - await fillInput(page, '#authorFilterText', 'Jane'); + await fillInput(page, '#authorFilterText', 'Jane', ['change']); await waitForEmptyTable(page); - await pressElement(page, '#reset-filters'); + await resetFilters(page); await waitForTableLength(page, 10); - await fillInput(page, '#authorFilterText', 'John'); + await fillInput(page, '#authorFilterText', 'John', ['change']); await waitForTableLength(page, 5); - await pressElement(page, '#reset-filters'); + await resetFilters(page); }); it('should successfully provide an easy-to-access button to filter in/out anonymous logs', async () => { @@ -154,17 +154,25 @@ module.exports = () => { }); it('can filter by creation date', async () => { - await pressElement(page, '#openFilterToggle'); + await openFilteringPanel(page); + + const popoverTrigger = '.createdAt-filter .popover-trigger'; + const popOverSelector = await getPopoverSelector(await page.$(popoverTrigger)); await waitForTableTotalRowsCountToEqual(page, 119); - // Insert a minimum date into the filter + const { fromDateSelector, toDateSelector, fromTimeSelector, toTimeSelector } = getPeriodInputsSelectors(popOverSelector); + const limit = '2020-02-02'; - await fillInput(page, '#createdFilterFrom', limit); - await fillInput(page, '#createdFilterTo', limit); - await waitForTableLength(page, 1); + + await fillInput(page, fromDateSelector, limit, ['change']); + await fillInput(page, toDateSelector, limit, ['change']); + await fillInput(page, fromTimeSelector, '11:00', ['change']); + await fillInput(page, toTimeSelector, '12:00', ['change']); - await pressElement(page, '#reset-filters'); + await waitForTableLength(page, 1); + await openFilteringPanel(page); + await resetFilters(page); }); it('can filter by tags', async () => { @@ -191,22 +199,20 @@ module.exports = () => { await pressElement(page, '#tag-filter-combination-operator-radio-button-or', true); await waitForTableLength(page, 3); - await pressElement(page, '#reset-filters'); + await resetFilters(page); }); it('can filter by environments', async () => { await waitForTableLength(page, 10); - await fillInput(page, '.environments-filter input', '8E4aZTjY'); + await fillInput(page, '.environments-filter input', '8E4aZTjY', ['change']); await waitForTableLength(page, 3); - - await pressElement(page, '#reset-filters'); + await resetFilters(page); await waitForTableLength(page, 10); - await fillInput(page, '.environments-filter input', 'abcdefgh'); + await fillInput(page, '.environments-filter input', 'abcdefgh', ['change']); await waitForEmptyTable(page); - - await pressElement(page, '#reset-filters'); + await resetFilters(page); }); it('can search for tag in the dropdown', async () => { @@ -234,31 +240,29 @@ module.exports = () => { await waitForTableLength(page, 10); // Insert some text into the filter - await fillInput(page, '#runsFilterText', '1, 2'); + await fillInput(page, '#runsFilterText', '1, 2', ['change']); await waitForTableLength(page, 2); + await resetFilters(page); - await pressElement(page, '#reset-filters'); await waitForTableLength(page, 10); - await fillInput(page, '#runsFilterText', '1234567890'); + await fillInput(page, '#runsFilterText', '1234567890', ['change']); await waitForEmptyTable(page); - - await pressElement(page, '#reset-filters'); + await resetFilters(page); }); it('can filter by lhc fill number', async () => { await waitForTableLength(page, 10); - await fillInput(page, '#lhcFillsFilter', '1, 6'); + await fillInput(page, '#lhcFillsFilterText', '1, 6', ['change']); await waitForTableLength(page, 1); + await resetFilters(page); - await pressElement(page, '#reset-filters'); await waitForTableLength(page, 10); - await fillInput(page, '#lhcFillsFilter', '1234567890'); + await fillInput(page, '#lhcFillsFilterText', '1234567890', ['change']); await waitForEmptyTable(page); - - await pressElement(page, '#reset-filters'); + await resetFilters(page); }); it('can sort by columns in ascending and descending manners', async () => {