diff --git a/changelog/entries/unreleased/bug/2289_higher_than_and_lower_than_frontend_filters_are_not_working_.json b/changelog/entries/unreleased/bug/2289_higher_than_and_lower_than_frontend_filters_are_not_working_.json new file mode 100644 index 000000000..2fa230e52 --- /dev/null +++ b/changelog/entries/unreleased/bug/2289_higher_than_and_lower_than_frontend_filters_are_not_working_.json @@ -0,0 +1,7 @@ +{ + "type": "bug", + "message": "Fix higher_than and lower_than frontend view filters for formula fields.", + "issue_number": 2289, + "bullet_points": [], + "created_at": "2024-01-17" +} \ No newline at end of file diff --git a/web-frontend/modules/database/components/row/ForeignRowEditModal.vue b/web-frontend/modules/database/components/row/ForeignRowEditModal.vue index f49130f14..aced27965 100644 --- a/web-frontend/modules/database/components/row/ForeignRowEditModal.vue +++ b/web-frontend/modules/database/components/row/ForeignRowEditModal.vue @@ -144,8 +144,6 @@ export default { this.$registry ) - console.log(newRowValues) - this.$store.dispatch('rowModal/updated', { tableId: this.tableId, values: newRowValues, diff --git a/web-frontend/modules/database/components/row/RowHistoryFieldDuration.vue b/web-frontend/modules/database/components/row/RowHistoryFieldDuration.vue index 817379999..7e4b7f02b 100644 --- a/web-frontend/modules/database/components/row/RowHistoryFieldDuration.vue +++ b/web-frontend/modules/database/components/row/RowHistoryFieldDuration.vue @@ -36,7 +36,11 @@ export default { methods: { formattedDuration(value) { const metadata = this.entry.fields_metadata[this.fieldIdentifier] - return DurationFieldType.formatValue(metadata, value) + const durationFieldType = this.$registry.get( + 'field', + DurationFieldType.getType() + ) + return durationFieldType.formatValue(metadata, value) }, }, } diff --git a/web-frontend/modules/database/components/view/grid/fields/FunctionalGridViewFieldDuration.vue b/web-frontend/modules/database/components/view/grid/fields/FunctionalGridViewFieldDuration.vue index 221d44742..40af413bd 100644 --- a/web-frontend/modules/database/components/view/grid/fields/FunctionalGridViewFieldDuration.vue +++ b/web-frontend/modules/database/components/view/grid/fields/FunctionalGridViewFieldDuration.vue @@ -7,14 +7,14 @@ </template> <script> -import { DurationFieldType } from '@baserow/modules/database/fieldTypes' +import { formatDurationValue } from '@baserow/modules/database/utils/duration' export default { name: 'FunctionalGridViewFieldDuration', functional: true, methods: { formatValue(field, value) { - return DurationFieldType.formatValue(field, value) + return formatDurationValue(value, field.duration_format) }, }, } diff --git a/web-frontend/modules/database/fieldTypes.js b/web-frontend/modules/database/fieldTypes.js index b8ab30c15..8f6a4d250 100644 --- a/web-frontend/modules/database/fieldTypes.js +++ b/web-frontend/modules/database/fieldTypes.js @@ -741,7 +741,7 @@ export class FieldType extends Registerable { * moment object, or a duration field can accept a string like '1:30' to * convert it to a number of seconds. */ - static parseInputValue(field, value) { + parseInputValue(field, value) { return value } } @@ -1351,7 +1351,7 @@ export class NumberFieldType extends FieldType { return true } - static parseInputValue(field, value) { + parseInputValue(field, value) { return parseFloat(value) } } @@ -1683,8 +1683,8 @@ class BaseDateFieldType extends FieldType { * correct format for the field. If it can't be parsed null is returned. */ prepareValueForPaste(field, clipboardData, richClipboardData) { - const dateValue = DateFieldType.parseInputValue(field, clipboardData || '') - return DateFieldType.formatDate(field, dateValue) + const dateValue = this.parseInputValue(field, clipboardData || '') + return this.formatValue(field, dateValue) } /** @@ -1724,7 +1724,7 @@ class BaseDateFieldType extends FieldType { return formats } - static parseInputValue(field, dateString) { + parseInputValue(field, dateString) { const formats = DateFieldType.getDateFormatsOptionsForValue( field, dateString @@ -1751,8 +1751,8 @@ class BaseDateFieldType extends FieldType { return date } - static formatDate(field, date) { - const momentDate = moment.utc(date) + formatValue(field, value) { + const momentDate = moment.utc(value) if (momentDate.isValid()) { return field.date_include_time ? momentDate.format() @@ -1838,9 +1838,9 @@ export class DateFieldType extends BaseDateFieldType { } parseQueryParameter(field, value) { - return DateFieldType.formatDate( + return this.formatValue( field.field, - DateFieldType.parseInputValue(field.field, value) + this.parseInputValue(field.field, value) ) } } @@ -2280,11 +2280,11 @@ export class DurationFieldType extends FieldType { } parseQueryParameter(field, value, options) { - return DurationFieldType.parseInputValue(field, value) + return this.parseInputValue(field.field, value) } toSearchableString(field, value, delimiter = ', ') { - return DurationFieldType.formatValue(field, value) + return this.formatValue(field, value) } getSort(name, order) { @@ -2350,22 +2350,22 @@ export class DurationFieldType extends FieldType { return RowHistoryFieldDuration } - static formatValue(field, value) { + formatValue(field, value) { return formatDurationValue(value, field.duration_format) } - static parseInputValue(field, value) { + parseInputValue(field, value) { const format = field.duration_format const preparedValue = parseDurationValue(value, format) return roundDurationValueToFormat(preparedValue, format) } toHumanReadableString(field, value, delimiter = ', ') { - return DurationFieldType.formatValue(field, value) + return this.formatValue(field, value) } prepareValueForCopy(field, value) { - return DurationFieldType.formatValue(field, value) + return this.formatValue(field, value) } prepareRichValueForCopy(field, value) { @@ -2376,7 +2376,7 @@ export class DurationFieldType extends FieldType { if (richClipboardData && isNumeric(richClipboardData)) { return richClipboardData } - return DurationFieldType.parseInputValue(field, clipboardData) + return this.parseInputValue(field, clipboardData) } getCanGroupByInView(field) { @@ -3543,6 +3543,14 @@ export class FormulaFieldType extends FieldType { const subType = this.app.$registry.get('formula_type', field.formula_type) return subType.canGroupByInView(field) } + + parseInputValue(field, value) { + const underlyingFieldType = this.app.$registry.get( + 'field', + this._mapFormulaTypeToFieldType(field.formula_type) + ) + return underlyingFieldType.parseInputValue(field, value) + } } export class CountFieldType extends FormulaFieldType { diff --git a/web-frontend/modules/database/mixins/durationField.js b/web-frontend/modules/database/mixins/durationField.js index eadd75f35..9a509ebb9 100644 --- a/web-frontend/modules/database/mixins/durationField.js +++ b/web-frontend/modules/database/mixins/durationField.js @@ -12,6 +12,11 @@ export default { formattedValue: '', } }, + computed: { + fieldType() { + return this.$registry.get('field', DurationFieldType.getType()) + }, + }, watch: { value(value) { this.updateFormattedValue(this.field, value) @@ -31,7 +36,7 @@ export default { return this.errorMsg }, formatValue(field, value) { - return DurationFieldType.formatValue(field, value) + return this.fieldType.formatValue(field, value) }, updateFormattedValue(field, value) { this.formattedValue = this.formatValue(field, value) @@ -41,7 +46,7 @@ export default { if (this.errorMsg !== null) { return } - const newCopy = DurationFieldType.parseInputValue(field, value) + const newCopy = this.fieldType.parseInputValue(field, value) if (newCopy !== this.copy) { this.copy = newCopy return newCopy diff --git a/web-frontend/modules/database/viewFilters.js b/web-frontend/modules/database/viewFilters.js index b3d4ad916..91fc2ec91 100644 --- a/web-frontend/modules/database/viewFilters.js +++ b/web-frontend/modules/database/viewFilters.js @@ -1341,13 +1341,9 @@ export class HigherThanViewFilterType extends ViewFilterType { return true } - rowValue = fieldType.constructor.parseInputValue(field, rowValue) - filterValue = fieldType.constructor.parseInputValue(field, filterValue) - return ( - Number.isFinite(rowValue) && - Number.isFinite(filterValue) && - rowValue > filterValue - ) + const rowVal = fieldType.parseInputValue(field, rowValue) + const fltVal = fieldType.parseInputValue(field, filterValue) + return Number.isFinite(rowVal) && Number.isFinite(fltVal) && rowVal > fltVal } } @@ -1388,13 +1384,9 @@ export class LowerThanViewFilterType extends ViewFilterType { return true } - rowValue = fieldType.constructor.parseInputValue(field, rowValue) - filterValue = fieldType.constructor.parseInputValue(field, filterValue) - return ( - Number.isFinite(rowValue) && - Number.isFinite(filterValue) && - rowValue < filterValue - ) + const rowVal = fieldType.parseInputValue(field, rowValue) + const fltVal = fieldType.parseInputValue(field, filterValue) + return Number.isFinite(rowVal) && Number.isFinite(fltVal) && rowVal < fltVal } } diff --git a/web-frontend/test/unit/database/fieldTypes.spec.js b/web-frontend/test/unit/database/fieldTypes.spec.js index d3b10f9a8..cea971cc5 100644 --- a/web-frontend/test/unit/database/fieldTypes.spec.js +++ b/web-frontend/test/unit/database/fieldTypes.spec.js @@ -603,17 +603,20 @@ const queryParametersForParsing = [ }, { fieldType: new DurationFieldType(), - input: { value: '1:01', field: { duration_format: 'h:mm' } }, + input: { value: '1:01', field: { field: { duration_format: 'h:mm' } } }, output: 3660, }, { fieldType: new DurationFieldType(), - input: { value: '1:01:01', field: { duration_format: 'h:mm:ss' } }, + input: { + value: '1:01:01', + field: { field: { duration_format: 'h:mm:ss' } }, + }, output: 3661, }, { fieldType: new DurationFieldType(), - input: { value: 61, field: { duration_format: 'h:mm' } }, + input: { value: 61, field: { field: { duration_format: 'h:mm' } } }, output: 60, // the value is rounded according to the duration format }, ] diff --git a/web-frontend/test/unit/database/viewFiltersMatch.spec.js b/web-frontend/test/unit/database/viewFiltersMatch.spec.js index 0de5101ca..eaf485957 100644 --- a/web-frontend/test/unit/database/viewFiltersMatch.spec.js +++ b/web-frontend/test/unit/database/viewFiltersMatch.spec.js @@ -31,7 +31,11 @@ import { HigherThanViewFilterType, LowerThanViewFilterType, } from '@baserow/modules/database/viewFilters' -import { DurationFieldType } from '@baserow/modules/database/fieldTypes' +import { + DurationFieldType, + NumberFieldType, + FormulaFieldType, +} from '@baserow/modules/database/fieldTypes' const dateBeforeCases = [ { @@ -1126,6 +1130,42 @@ const durationLowerThanCases = [ }, ] +const numberValueIsHigherThanCases = [ + { + rowValue: 2, + filterValue: 0, + expected: true, + }, + { + rowValue: null, + filterValue: 0, + expected: false, + }, + { + rowValue: 0, + filterValue: '0', + expected: false, + }, +] + +const numberValueIsLowerThanCases = [ + { + rowValue: 1, + filterValue: '2', + expected: true, + }, + { + rowValue: null, + filterValue: 0, + expected: false, + }, + { + rowValue: 0, + filterValue: '0', + expected: false, + }, +] + describe('All Tests', () => { let testApp = null @@ -1405,10 +1445,28 @@ describe('All Tests', () => { ).toBe(true) }) - test.each(durationHigherThanCases)('HigherThanFilterType', (values) => { - const fieldType = new DurationFieldType() + test.each(durationHigherThanCases)( + 'DurationHigherThanFilterType', + (values) => { + const fieldType = new DurationFieldType({ + app: testApp, + }) + const { field } = values.context + const result = new HigherThanViewFilterType({ app: testApp }).matches( + values.rowValue, + values.filterValue, + field, + fieldType + ) + expect(result).toBe(values.expected) + } + ) + + test.each(durationLowerThanCases)('DurationLowerThanFilterType', (values) => { + const app = testApp.getApp() + const fieldType = new DurationFieldType({ app }) const { field } = values.context - const result = new HigherThanViewFilterType({ app: testApp }).matches( + const result = new LowerThanViewFilterType({ app }).matches( values.rowValue, values.filterValue, field, @@ -1417,15 +1475,59 @@ describe('All Tests', () => { expect(result).toBe(values.expected) }) - test.each(durationLowerThanCases)('LowerThanFilterType', (values) => { - const fieldType = new DurationFieldType() - const { field } = values.context - const result = new LowerThanViewFilterType({ app: testApp }).matches( - values.rowValue, - values.filterValue, - field, - fieldType - ) - expect(result).toBe(values.expected) - }) + test.each(numberValueIsHigherThanCases)( + 'NumberHigherThanFilterType', + (values) => { + const app = testApp.getApp() + const result = new HigherThanViewFilterType({ app }).matches( + values.rowValue, + values.filterValue, + { type: 'number' }, + new NumberFieldType({ app }) + ) + expect(result).toBe(values.expected) + } + ) + + test.each(numberValueIsHigherThanCases)( + 'FormulaNumberHigherThanFilterType', + (values) => { + const app = testApp.getApp() + const result = new HigherThanViewFilterType({ app }).matches( + values.rowValue, + values.filterValue, + { type: 'formula', formula_type: 'number' }, + new FormulaFieldType({ app }) + ) + expect(result).toBe(values.expected) + } + ) + + test.each(numberValueIsLowerThanCases)( + 'NumberLowerThanFilterType', + (values) => { + const app = testApp.getApp() + const result = new LowerThanViewFilterType({ app }).matches( + values.rowValue, + values.filterValue, + { type: 'number' }, + new NumberFieldType({ app }) + ) + expect(result).toBe(values.expected) + } + ) + + test.each(numberValueIsLowerThanCases)( + 'FormulaNumberLowerThanFilterType', + (values) => { + const app = testApp.getApp() + const result = new LowerThanViewFilterType({ app }).matches( + values.rowValue, + values.filterValue, + { type: 'formula', formula_type: 'number' }, + new FormulaFieldType({ app }) + ) + expect(result).toBe(values.expected) + } + ) })