From 68aab05d482fbd8b2e60673408a445141aaf0682 Mon Sep 17 00:00:00 2001 From: Semjon Geist Date: Sun, 19 Feb 2023 19:07:03 +0100 Subject: [PATCH 001/404] add feature to set custom pvc access mode, e.g. read-write-many --- charts/nocodb/templates/pvc.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/nocodb/templates/pvc.yaml b/charts/nocodb/templates/pvc.yaml index 67656c01d1..092a1b0439 100644 --- a/charts/nocodb/templates/pvc.yaml +++ b/charts/nocodb/templates/pvc.yaml @@ -5,10 +5,10 @@ metadata: labels: {{- include "nocodb.selectorLabels" . | nindent 8 }} spec: - accessModes: - - ReadWriteMany resources: requests: storage: {{ .Values.storage.size }} storageClassName: {{ .Values.storage.storageClassName }} + accessModes: + {{- default (toYaml .Values.storage.accessModes) "- ReadWriteMany" | nindent 4 }} volumeMode: Filesystem From 31a3d84524f79aa3001d4e01151129c6813f0e33 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Wed, 22 Feb 2023 16:03:28 +0800 Subject: [PATCH 002/404] feat(nc-gui): include date & datetimes for >, <, >= and <= --- packages/nc-gui/utils/filterUtils.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/nc-gui/utils/filterUtils.ts b/packages/nc-gui/utils/filterUtils.ts index 599a5cb5dc..22e78dd45d 100644 --- a/packages/nc-gui/utils/filterUtils.ts +++ b/packages/nc-gui/utils/filterUtils.ts @@ -156,22 +156,22 @@ export const comparisonOpList = ( { text: '>', value: 'gt', - includedTypes: [...numericUITypes], + includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime], }, { text: '<', value: 'lt', - includedTypes: [...numericUITypes], + includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime], }, { text: '>=', value: 'gte', - includedTypes: [...numericUITypes], + includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime], }, { text: '<=', value: 'lte', - includedTypes: [...numericUITypes], + includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime], }, { text: 'is blank', From 00ab683b94dd4c3f0bbf127982ebea41b795fcfb Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Wed, 22 Feb 2023 16:25:33 +0800 Subject: [PATCH 003/404] fix(nocodb): disable filtering if empty value is given for date & datetimes --- .../src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts index ce0ec02ad8..e659948d3e 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts @@ -271,13 +271,8 @@ const parseConditionV2 = async ( return (qb: Knex.QueryBuilder) => { let [field, val] = [_field, _val]; - if ( - [UITypes.Date, UITypes.DateTime].includes(column.uidt) && - !val && - ['is', 'isnot'].includes(filter.comparison_op) - ) { - // for date & datetime, - // val cannot be empty for non-is & non-isnot filters + if ([UITypes.Date, UITypes.DateTime].includes(column.uidt) && !val) { + // for date & datetime, val cannot be empty for all filters return; } From 520039c0c7b496668d8ac599e6b83ce3f12c5e19 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Wed, 22 Feb 2023 17:41:07 +0800 Subject: [PATCH 004/404] feat(nc-gui): render >, <, >=, <= text based on field ui type --- packages/nc-gui/utils/filterUtils.ts | 36 ++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/packages/nc-gui/utils/filterUtils.ts b/packages/nc-gui/utils/filterUtils.ts index 22e78dd45d..8074d693b0 100644 --- a/packages/nc-gui/utils/filterUtils.ts +++ b/packages/nc-gui/utils/filterUtils.ts @@ -32,6 +32,34 @@ const getNotLikeText = (fieldUiType: UITypes) => { return 'is not like' } +const getGtText = (fieldUiType: UITypes) => { + if ([UITypes.Date, UITypes.DateTime].includes(fieldUiType)) { + return 'is after' + } + return '>' +} + +const getLtText = (fieldUiType: UITypes) => { + if ([UITypes.Date, UITypes.DateTime].includes(fieldUiType)) { + return 'is before' + } + return '<' +} + +const getGteText = (fieldUiType: UITypes) => { + if ([UITypes.Date, UITypes.DateTime].includes(fieldUiType)) { + return 'is on or after' + } + return '>=' +} + +const getLteText = (fieldUiType: UITypes) => { + if ([UITypes.Date, UITypes.DateTime].includes(fieldUiType)) { + return 'is on or before' + } + return '<=' +} + export const comparisonOpList = ( fieldUiType: UITypes, ): { @@ -154,22 +182,22 @@ export const comparisonOpList = ( includedTypes: [UITypes.MultiSelect, UITypes.SingleSelect], }, { - text: '>', + text: getGtText(fieldUiType), value: 'gt', includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime], }, { - text: '<', + text: getLtText(fieldUiType), value: 'lt', includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime], }, { - text: '>=', + text: getGteText(fieldUiType), value: 'gte', includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime], }, { - text: '<=', + text: getLteText(fieldUiType), value: 'lte', includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime], }, From dd8729875079cfedcbce6e52f49f70d7755bcc0f Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Wed, 22 Feb 2023 18:07:45 +0800 Subject: [PATCH 005/404] feat(nocodb): ncFilterUpgrader_0105003 --- .../src/lib/version-upgrader/NcUpgrader.ts | 2 + .../ncFilterUpgrader_0105003.ts | 38 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 packages/nocodb/src/lib/version-upgrader/ncFilterUpgrader_0105003.ts diff --git a/packages/nocodb/src/lib/version-upgrader/NcUpgrader.ts b/packages/nocodb/src/lib/version-upgrader/NcUpgrader.ts index c125701a4c..e4354301eb 100644 --- a/packages/nocodb/src/lib/version-upgrader/NcUpgrader.ts +++ b/packages/nocodb/src/lib/version-upgrader/NcUpgrader.ts @@ -13,6 +13,7 @@ import ncAttachmentUpgrader from './ncAttachmentUpgrader'; import ncAttachmentUpgrader_0104002 from './ncAttachmentUpgrader_0104002'; import ncStickyColumnUpgrader from './ncStickyColumnUpgrader'; import ncFilterUpgrader_0104004 from './ncFilterUpgrader_0104004'; +import ncFilterUpgrader_0105003 from './ncFilterUpgrader_0105003'; const log = debug('nc:version-upgrader'); import boxen from 'boxen'; @@ -45,6 +46,7 @@ export default class NcUpgrader { { name: '0104002', handler: ncAttachmentUpgrader_0104002 }, { name: '0104004', handler: ncFilterUpgrader_0104004 }, { name: '0105002', handler: ncStickyColumnUpgrader }, + { name: '0105003', handler: ncFilterUpgrader_0105003 }, ]; if (!(await ctx.ncMeta.knexConnection?.schema?.hasTable?.('nc_store'))) { return; diff --git a/packages/nocodb/src/lib/version-upgrader/ncFilterUpgrader_0105003.ts b/packages/nocodb/src/lib/version-upgrader/ncFilterUpgrader_0105003.ts new file mode 100644 index 0000000000..a9252de580 --- /dev/null +++ b/packages/nocodb/src/lib/version-upgrader/ncFilterUpgrader_0105003.ts @@ -0,0 +1,38 @@ +import { NcUpgraderCtx } from './NcUpgrader'; +import { MetaTable } from '../utils/globals'; +import NcMetaIO from '../meta/NcMetaIO'; +import Column from '../models/Column'; +import Filter from '../models/Filter'; +import { UITypes } from 'nocodb-sdk'; + +// as of 0.105.3, date / datetime filters include `is like` and `is not like` which are not practical +// this upgrader is simply to remove them + +// Change Summary: +// - Date / DateTime columns: remove `is like` and `is not like` + +const removeLikeFilters = (filter, ncMeta) => { + let actions = []; + // remove `is like` and `is not like` + if (['like', 'nlike'].includes(filter.comparison_op)) { + actions.push(Filter.delete(filter.id, ncMeta)); + } + return actions; +}; + +async function removeFilters(ncMeta: NcMetaIO) { + const filters = await ncMeta.metaList2(null, null, MetaTable.FILTER_EXP); + for (const filter of filters) { + if (!filter.fk_column_id || filter.is_group) { + continue; + } + const col = await Column.get({ colId: filter.fk_column_id }, ncMeta); + if ([UITypes.Date, UITypes.DateTime].includes(col.uidt)) { + await Promise.all(removeLikeFilters(filter, ncMeta)); + } + } +} + +export default async function ({ ncMeta }: NcUpgraderCtx) { + await removeFilters(ncMeta); +} From 80c6ace33ebcc325e226915da2ce8c7357991763 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Wed, 22 Feb 2023 18:40:00 +0800 Subject: [PATCH 006/404] feat(nocodb): add a migration script nc_026_add_comparison_sub_op to add a new field comparison_sub_op for FILTER_EXP --- .../src/lib/migrations/XcMigrationSourcev2.ts | 4 ++++ .../v2/nc_026_add_comparison_sub_op.ts | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 packages/nocodb/src/lib/migrations/v2/nc_026_add_comparison_sub_op.ts diff --git a/packages/nocodb/src/lib/migrations/XcMigrationSourcev2.ts b/packages/nocodb/src/lib/migrations/XcMigrationSourcev2.ts index b6e42c5b8c..2f28ae814c 100644 --- a/packages/nocodb/src/lib/migrations/XcMigrationSourcev2.ts +++ b/packages/nocodb/src/lib/migrations/XcMigrationSourcev2.ts @@ -13,6 +13,7 @@ import * as nc_022_qr_code_column_type from './v2/nc_022_qr_code_column_type'; import * as nc_023_multiple_source from './v2/nc_023_multiple_source'; import * as nc_024_barcode_column_type from './v2/nc_024_barcode_column_type'; import * as nc_025_add_row_height from './v2/nc_025_add_row_height'; +import * as nc_026_add_comparison_sub_op from './v2/nc_026_add_comparison_sub_op'; // Create a custom migration source class export default class XcMigrationSourcev2 { @@ -37,6 +38,7 @@ export default class XcMigrationSourcev2 { 'nc_023_multiple_source', 'nc_024_barcode_column_type', 'nc_025_add_row_height', + 'nc_026_add_comparison_sub_op', ]); } @@ -76,6 +78,8 @@ export default class XcMigrationSourcev2 { return nc_024_barcode_column_type; case 'nc_025_add_row_height': return nc_025_add_row_height; + case 'nc_026_add_comparison_sub_op': + return nc_026_add_comparison_sub_op; } } } diff --git a/packages/nocodb/src/lib/migrations/v2/nc_026_add_comparison_sub_op.ts b/packages/nocodb/src/lib/migrations/v2/nc_026_add_comparison_sub_op.ts new file mode 100644 index 0000000000..4e43488320 --- /dev/null +++ b/packages/nocodb/src/lib/migrations/v2/nc_026_add_comparison_sub_op.ts @@ -0,0 +1,16 @@ +import { Knex } from 'knex'; +import { MetaTable } from '../../utils/globals'; + +const up = async (knex: Knex) => { + await knex.schema.alterTable(MetaTable.FILTER_EXP, (table) => { + table.string('comparison_sub_op'); + }); +}; + +const down = async (knex) => { + await knex.schema.alterTable(MetaTable.FILTER_EXP, (table) => { + table.dropColumns('comparison_sub_op'); + }); +}; + +export { up, down }; From c82a4f63d0075cb294277103cddbce9427e10701 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Wed, 22 Feb 2023 18:49:13 +0800 Subject: [PATCH 007/404] feat(nocodb-sdk): add comparison_sub_op --- packages/nocodb-sdk/src/lib/Api.ts | 1 + scripts/sdk/swagger.json | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/packages/nocodb-sdk/src/lib/Api.ts b/packages/nocodb-sdk/src/lib/Api.ts index a72029e453..ae78b1e92f 100644 --- a/packages/nocodb-sdk/src/lib/Api.ts +++ b/packages/nocodb-sdk/src/lib/Api.ts @@ -191,6 +191,7 @@ export interface FilterType { fk_column_id?: string; logical_op?: string; comparison_op?: string; + comparison_sub_op?: string; value?: string | number | boolean | null; is_group?: boolean; children?: FilterType[]; diff --git a/scripts/sdk/swagger.json b/scripts/sdk/swagger.json index 796123f9ff..b9ae402ead 100644 --- a/scripts/sdk/swagger.json +++ b/scripts/sdk/swagger.json @@ -1914,6 +1914,7 @@ "fk_column_id": "string", "logical_op": "string", "comparison_op": "string", + "comparison_sub_op": "string", "value": "string", "is_group": true, "children": [ @@ -7784,6 +7785,7 @@ "fk_column_id": "string", "logical_op": "string", "comparison_op": "string", + "comparison_sub_op": "string", "value": "string", "is_group": true, "children": [ @@ -7994,6 +7996,9 @@ "comparison_op": { "type": "string" }, + "comparison_sub_op": { + "type": "string" + }, "value": { "type": [ "string", From 872eb89022b57bce77b8b3795ae83390f3dba457 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Wed, 22 Feb 2023 19:16:30 +0800 Subject: [PATCH 008/404] feat(nocodb): add comparison_sub_op in Filter.ts --- packages/nocodb/src/lib/models/Filter.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/nocodb/src/lib/models/Filter.ts b/packages/nocodb/src/lib/models/Filter.ts index 1e6a0dfaee..1267e2a5e9 100644 --- a/packages/nocodb/src/lib/models/Filter.ts +++ b/packages/nocodb/src/lib/models/Filter.ts @@ -52,6 +52,19 @@ export default class Filter { | 'is' | 'btw' | 'nbtw'; + + comparison_sub_op?: + | 'today' + | 'tomorrow' + | 'yesterday' + | 'one_week_ago' + | 'one_week_from_now' + | 'one_month_ago' + | 'one_month_from_now' + | 'number_of_days_ago' + | 'number_of_days_from_now' + | 'exact_date'; + value?: string; logical_op?: string; @@ -86,6 +99,7 @@ export default class Filter { 'fk_hook_id', 'fk_column_id', 'comparison_op', + 'comparison_sub_op', 'value', 'fk_parent_id', 'is_group', @@ -223,6 +237,7 @@ export default class Filter { const updateObj = extractProps(filter, [ 'fk_column_id', 'comparison_op', + 'comparison_sub_op', 'value', 'fk_parent_id', 'is_group', From 64a32b998e363b4be43dadf35102fdee5df95d68 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Wed, 22 Feb 2023 19:27:16 +0800 Subject: [PATCH 009/404] feat(nc-gui): add operationSub --- packages/nc-gui/lang/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/nc-gui/lang/en.json b/packages/nc-gui/lang/en.json index bdbc84d36d..af9c3cd63a 100644 --- a/packages/nc-gui/lang/en.json +++ b/packages/nc-gui/lang/en.json @@ -232,6 +232,7 @@ "action": "Action", "actions": "Actions", "operation": "Operation", + "operationSub": "Sub Operation", "operationType": "Operation type", "operationSubType": "Operation sub-type", "description": "Description", From 06aa2f78e92ea397845539f257e9f56f77a93941 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Wed, 22 Feb 2023 19:27:54 +0800 Subject: [PATCH 010/404] feat(nc-gui): display is / is not for date & datetime --- packages/nc-gui/utils/filterUtils.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/nc-gui/utils/filterUtils.ts b/packages/nc-gui/utils/filterUtils.ts index 8074d693b0..667c164af8 100644 --- a/packages/nc-gui/utils/filterUtils.ts +++ b/packages/nc-gui/utils/filterUtils.ts @@ -3,7 +3,11 @@ import { UITypes, isNumericCol, numericUITypes } from 'nocodb-sdk' const getEqText = (fieldUiType: UITypes) => { if (isNumericCol(fieldUiType)) { return '=' - } else if ([UITypes.SingleSelect, UITypes.Collaborator, UITypes.LinkToAnotherRecord].includes(fieldUiType)) { + } else if ( + [UITypes.SingleSelect, UITypes.Collaborator, UITypes.LinkToAnotherRecord, UITypes.Date, UITypes.DateTime].includes( + fieldUiType, + ) + ) { return 'is' } return 'is equal' @@ -12,7 +16,11 @@ const getEqText = (fieldUiType: UITypes) => { const getNeqText = (fieldUiType: UITypes) => { if (isNumericCol(fieldUiType)) { return '!=' - } else if ([UITypes.SingleSelect, UITypes.Collaborator, UITypes.LinkToAnotherRecord].includes(fieldUiType)) { + } else if ( + [UITypes.SingleSelect, UITypes.Collaborator, UITypes.LinkToAnotherRecord, UITypes.Date, UITypes.DateTime].includes( + fieldUiType, + ) + ) { return 'is not' } return 'is not equal' From 7877c97d8d8bd9e7c67f1e2777ec086dccd04f1c Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Wed, 22 Feb 2023 19:43:50 +0800 Subject: [PATCH 011/404] feat(nc-gui): add comparisonSubOpList --- .../smartsheet/toolbar/ColumnFilter.vue | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue b/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue index 095e816510..2d9495ea82 100644 --- a/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue +++ b/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue @@ -6,6 +6,7 @@ import { MetaInj, ReloadViewDataHookInj, comparisonOpList, + comparisonSubOpList, computed, inject, ref, @@ -54,6 +55,7 @@ const { sync, saveOrUpdateDebounced, isComparisonOpAllowed, + isComparisonSubOpAllowed, } = useViewFilters( activeView, parentId, @@ -75,9 +77,10 @@ const filterPrevComparisonOp = ref>({}) const filterUpdateCondition = (filter: FilterType, i: number) => { const col = getColumn(filter) + if (!col) return if ( col.uidt === UITypes.SingleSelect && - ['anyof', 'nanyof'].includes(filterPrevComparisonOp.value[filter.id]) && + ['anyof', 'nanyof'].includes(filterPrevComparisonOp.value[filter.id!]) && ['eq', 'neq'].includes(filter.comparison_op!) ) { // anyof and nanyof can allow multiple selections, @@ -93,6 +96,7 @@ const filterUpdateCondition = (filter: FilterType, i: number) => { $e('a:filter:update', { logical: filter.logical_op, comparison: filter.comparison_op, + comparison_sub_op: filter.comparison_sub_op, }) } @@ -109,7 +113,7 @@ const types = computed(() => { watch( () => activeView.value?.id, - (n: string, o: string) => { + (n, o) => { // if nested no need to reload since it will get reloaded from parent if (!nested && n !== o && (hookId || !webHook)) loadFilters(hookId as string) }, @@ -137,14 +141,21 @@ const applyChanges = async (hookId?: string, _nested = false) => { } const selectFilterField = (filter: Filter, index: number) => { + const col = getColumn(filter) + if (!col) return // when we change the field, // the corresponding default filter operator needs to be changed as well // since the existing one may not be supported for the new field // e.g. `eq` operator is not supported in checkbox field // hence, get the first option of the supported operators of the new field - filter.comparison_op = comparisonOpList(getColumn(filter)!.uidt as UITypes).filter((compOp) => + filter.comparison_op = comparisonOpList(col.uidt as UITypes).filter((compOp) => isComparisonOpAllowed(filter, compOp), )?.[0].value + + if ([UITypes.Date, UITypes.DateTime].includes(col.uidt as UITypes) && !['blank', 'notblank'].includes(filter.comparison_op)) { + filter.comparison_sub_op = 'exact_date' + } + // reset filter value as well filter.value = '' saveOrUpdate(filter, index) @@ -260,6 +271,28 @@ defineExpose({ + + + - + + Date: Thu, 23 Feb 2023 15:07:50 +0800 Subject: [PATCH 016/404] chore(nocodb): reorder lt, le, lte in conditionV2 --- .../db/sql-data-mapper/lib/sql/conditionV2.ts | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts index e659948d3e..dfe0d061e3 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts @@ -476,6 +476,27 @@ const parseConditionV2 = async ( } } break; + case 'lt': + const lt_op = customWhereClause ? '>' : '<'; + qb = qb.where(field, lt_op, val); + if (column.uidt === UITypes.Rating) { + // unset number is considered as NULL + if (lt_op === '<' && val > 0) { + qb = qb.orWhereNull(field); + } + } + break; + case 'le': + case 'lte': + const le_op = customWhereClause ? '>=' : '<='; + qb = qb.where(field, le_op, val); + if (column.uidt === UITypes.Rating) { + // unset number is considered as NULL + if (le_op === '<=' || (le_op === '>=' && val === 0)) { + qb = qb.orWhereNull(field); + } + } + break; case 'in': qb = qb.whereIn( field, @@ -512,27 +533,6 @@ const parseConditionV2 = async ( else if (filter.value === 'false') qb = qb.whereNot(customWhereClause || field, false); break; - case 'lt': - const lt_op = customWhereClause ? '>' : '<'; - qb = qb.where(field, lt_op, val); - if (column.uidt === UITypes.Rating) { - // unset number is considered as NULL - if (lt_op === '<' && val > 0) { - qb = qb.orWhereNull(field); - } - } - break; - case 'le': - case 'lte': - const le_op = customWhereClause ? '>=' : '<='; - qb = qb.where(field, le_op, val); - if (column.uidt === UITypes.Rating) { - // unset number is considered as NULL - if (le_op === '<=' || (le_op === '>=' && val === 0)) { - qb = qb.orWhereNull(field); - } - } - break; case 'empty': if (column.uidt === UITypes.Formula) { [field, val] = [val, field]; From 889f42f971fe7ab74123d51f381245e42ca71b43 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 23 Feb 2023 15:31:38 +0800 Subject: [PATCH 017/404] feat(nocodb): parseDateOrDateTimeCondition --- .../db/sql-data-mapper/lib/sql/conditionV2.ts | 179 +++++++++++------- 1 file changed, 106 insertions(+), 73 deletions(-) diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts index dfe0d061e3..508a3aee60 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts @@ -35,6 +35,15 @@ function getLogicalOpMethod(filter: Filter) { } } +function parseDateOrDateTimeCondition( + qb: Knex.QueryBuilder, + op: '>' | '<' | '>=' | '<=' | '=' | '!=', + val: any +) { + // TODO: + console.log(qb, op, val); +} + const parseConditionV2 = async ( _filter: Filter | Filter[], knex: XKnex, @@ -283,71 +292,79 @@ const parseConditionV2 = async ( switch (filter.comparison_op) { case 'eq': - if (qb?.client?.config?.client === 'mysql2') { - if ( - [ - UITypes.Duration, - UITypes.Currency, - UITypes.Percent, - UITypes.Number, - UITypes.Decimal, - UITypes.Rating, - UITypes.Rollup, - ].includes(column.uidt) - ) { - qb = qb.where(field, val); + if ([UITypes.Date, UITypes.DateTime].includes(column.uidt)) { + parseDateOrDateTimeCondition(qb, '=', val); + } else { + if (qb?.client?.config?.client === 'mysql2') { + if ( + [ + UITypes.Duration, + UITypes.Currency, + UITypes.Percent, + UITypes.Number, + UITypes.Decimal, + UITypes.Rating, + UITypes.Rollup, + ].includes(column.uidt) + ) { + qb = qb.where(field, val); + } else { + // mysql is case-insensitive for strings, turn to case-sensitive + qb = qb.whereRaw('BINARY ?? = ?', [field, val]); + } } else { - // mysql is case-insensitive for strings, turn to case-sensitive - qb = qb.whereRaw('BINARY ?? = ?', [field, val]); + qb = qb.where(field, val); + } + if (column.uidt === UITypes.Rating && val === 0) { + // unset rating is considered as NULL + qb = qb.orWhereNull(field); } - } else { - qb = qb.where(field, val); - } - if (column.uidt === UITypes.Rating && val === 0) { - // unset rating is considered as NULL - qb = qb.orWhereNull(field); } break; case 'neq': case 'not': - if (qb?.client?.config?.client === 'mysql2') { - if ( - [ - UITypes.Duration, - UITypes.Currency, - UITypes.Percent, - UITypes.Number, - UITypes.Decimal, - UITypes.Rollup, - ].includes(column.uidt) - ) { - qb = qb.where((nestedQb) => { - nestedQb - .whereNot(field, val) - .orWhereNull(customWhereClause ? _val : _field); - }); - } else if (column.uidt === UITypes.Rating) { - // unset rating is considered as NULL - if (val === 0) { - qb = qb.whereNot(field, val).whereNotNull(field); + if ([UITypes.Date, UITypes.DateTime].includes(column.uidt)) { + parseDateOrDateTimeCondition(qb, '!=', val); + } else { + if (qb?.client?.config?.client === 'mysql2') { + if ( + [ + UITypes.Duration, + UITypes.Currency, + UITypes.Percent, + UITypes.Number, + UITypes.Decimal, + UITypes.Rollup, + ].includes(column.uidt) + ) { + qb = qb.where((nestedQb) => { + nestedQb + .whereNot(field, val) + .orWhereNull(customWhereClause ? _val : _field); + }); + } else if (column.uidt === UITypes.Rating) { + // unset rating is considered as NULL + if (val === 0) { + qb = qb.whereNot(field, val).whereNotNull(field); + } else { + qb = qb.whereNot(field, val).orWhereNull(field); + } } else { - qb = qb.whereNot(field, val).orWhereNull(field); + // mysql is case-insensitive for strings, turn to case-sensitive + qb = qb.where((nestedQb) => { + nestedQb.whereRaw('BINARY ?? != ?', [field, val]); + if (column.uidt !== UITypes.Rating) { + nestedQb.orWhereNull(customWhereClause ? _val : _field); + } + }); } } else { - // mysql is case-insensitive for strings, turn to case-sensitive qb = qb.where((nestedQb) => { - nestedQb.whereRaw('BINARY ?? != ?', [field, val]); - if (column.uidt !== UITypes.Rating) { - nestedQb.orWhereNull(customWhereClause ? _val : _field); - } + nestedQb + .whereNot(field, val) + .orWhereNull(customWhereClause ? _val : _field); }); } - } else { - qb = qb.where((nestedQb) => { - nestedQb - .whereNot(field, val) - .orWhereNull(customWhereClause ? _val : _field); - }); } break; case 'like': @@ -457,43 +474,59 @@ const parseConditionV2 = async ( break; case 'gt': const gt_op = customWhereClause ? '<' : '>'; - qb = qb.where(field, gt_op, val); - if (column.uidt === UITypes.Rating) { - // unset rating is considered as NULL - if (gt_op === '<' && val > 0) { - qb = qb.orWhereNull(field); + if ([UITypes.Date, UITypes.DateTime].includes(column.uidt)) { + parseDateOrDateTimeCondition(qb, gt_op, val); + } else { + qb = qb.where(field, gt_op, val); + if (column.uidt === UITypes.Rating) { + // unset rating is considered as NULL + if (gt_op === '<' && val > 0) { + qb = qb.orWhereNull(field); + } } } break; case 'ge': case 'gte': const ge_op = customWhereClause ? '<=' : '>='; - qb = qb.where(field, ge_op, val); - if (column.uidt === UITypes.Rating) { - // unset rating is considered as NULL - if (ge_op === '<=' || (ge_op === '>=' && val === 0)) { - qb = qb.orWhereNull(field); + if ([UITypes.Date, UITypes.DateTime].includes(column.uidt)) { + parseDateOrDateTimeCondition(qb, ge_op, val); + } else { + qb = qb.where(field, ge_op, val); + if (column.uidt === UITypes.Rating) { + // unset rating is considered as NULL + if (ge_op === '<=' || (ge_op === '>=' && val === 0)) { + qb = qb.orWhereNull(field); + } } } break; case 'lt': const lt_op = customWhereClause ? '>' : '<'; - qb = qb.where(field, lt_op, val); - if (column.uidt === UITypes.Rating) { - // unset number is considered as NULL - if (lt_op === '<' && val > 0) { - qb = qb.orWhereNull(field); + if ([UITypes.Date, UITypes.DateTime].includes(column.uidt)) { + parseDateOrDateTimeCondition(qb, lt_op, val); + } else { + qb = qb.where(field, lt_op, val); + if (column.uidt === UITypes.Rating) { + // unset number is considered as NULL + if (lt_op === '<' && val > 0) { + qb = qb.orWhereNull(field); + } } } break; case 'le': case 'lte': const le_op = customWhereClause ? '>=' : '<='; - qb = qb.where(field, le_op, val); - if (column.uidt === UITypes.Rating) { - // unset number is considered as NULL - if (le_op === '<=' || (le_op === '>=' && val === 0)) { - qb = qb.orWhereNull(field); + if ([UITypes.Date, UITypes.DateTime].includes(column.uidt)) { + parseDateOrDateTimeCondition(qb, le_op, val); + } else { + qb = qb.where(field, le_op, val); + if (column.uidt === UITypes.Rating) { + // unset number is considered as NULL + if (le_op === '<=' || (le_op === '>=' && val === 0)) { + qb = qb.orWhereNull(field); + } } } break; From 0a67a812e3ca2568746d028ceae36d7c5ef581f0 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 23 Feb 2023 15:44:01 +0800 Subject: [PATCH 018/404] refactor(nocodb): remove parseDateOrDateTimeCondition --- .../db/sql-data-mapper/lib/sql/conditionV2.ts | 189 ++++++++---------- 1 file changed, 80 insertions(+), 109 deletions(-) diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts index 508a3aee60..68c6c54547 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts @@ -35,15 +35,6 @@ function getLogicalOpMethod(filter: Filter) { } } -function parseDateOrDateTimeCondition( - qb: Knex.QueryBuilder, - op: '>' | '<' | '>=' | '<=' | '=' | '!=', - val: any -) { - // TODO: - console.log(qb, op, val); -} - const parseConditionV2 = async ( _filter: Filter | Filter[], knex: XKnex, @@ -280,9 +271,13 @@ const parseConditionV2 = async ( return (qb: Knex.QueryBuilder) => { let [field, val] = [_field, _val]; - if ([UITypes.Date, UITypes.DateTime].includes(column.uidt) && !val) { - // for date & datetime, val cannot be empty for all filters - return; + if ([UITypes.Date, UITypes.DateTime].includes(column.uidt)) { + if (!val) { + // for date & datetime, val cannot be empty for all filters + return; + } + // handle sub operation + // TODO: } if (isNumericCol(column.uidt) && typeof val === 'string') { @@ -292,79 +287,71 @@ const parseConditionV2 = async ( switch (filter.comparison_op) { case 'eq': - if ([UITypes.Date, UITypes.DateTime].includes(column.uidt)) { - parseDateOrDateTimeCondition(qb, '=', val); - } else { - if (qb?.client?.config?.client === 'mysql2') { - if ( - [ - UITypes.Duration, - UITypes.Currency, - UITypes.Percent, - UITypes.Number, - UITypes.Decimal, - UITypes.Rating, - UITypes.Rollup, - ].includes(column.uidt) - ) { - qb = qb.where(field, val); - } else { - // mysql is case-insensitive for strings, turn to case-sensitive - qb = qb.whereRaw('BINARY ?? = ?', [field, val]); - } - } else { + if (qb?.client?.config?.client === 'mysql2') { + if ( + [ + UITypes.Duration, + UITypes.Currency, + UITypes.Percent, + UITypes.Number, + UITypes.Decimal, + UITypes.Rating, + UITypes.Rollup, + ].includes(column.uidt) + ) { qb = qb.where(field, val); + } else { + // mysql is case-insensitive for strings, turn to case-sensitive + qb = qb.whereRaw('BINARY ?? = ?', [field, val]); } - if (column.uidt === UITypes.Rating && val === 0) { - // unset rating is considered as NULL - qb = qb.orWhereNull(field); - } + } else { + qb = qb.where(field, val); + } + if (column.uidt === UITypes.Rating && val === 0) { + // unset rating is considered as NULL + qb = qb.orWhereNull(field); } break; case 'neq': case 'not': - if ([UITypes.Date, UITypes.DateTime].includes(column.uidt)) { - parseDateOrDateTimeCondition(qb, '!=', val); - } else { - if (qb?.client?.config?.client === 'mysql2') { - if ( - [ - UITypes.Duration, - UITypes.Currency, - UITypes.Percent, - UITypes.Number, - UITypes.Decimal, - UITypes.Rollup, - ].includes(column.uidt) - ) { - qb = qb.where((nestedQb) => { - nestedQb - .whereNot(field, val) - .orWhereNull(customWhereClause ? _val : _field); - }); - } else if (column.uidt === UITypes.Rating) { - // unset rating is considered as NULL - if (val === 0) { - qb = qb.whereNot(field, val).whereNotNull(field); - } else { - qb = qb.whereNot(field, val).orWhereNull(field); - } - } else { - // mysql is case-insensitive for strings, turn to case-sensitive - qb = qb.where((nestedQb) => { - nestedQb.whereRaw('BINARY ?? != ?', [field, val]); - if (column.uidt !== UITypes.Rating) { - nestedQb.orWhereNull(customWhereClause ? _val : _field); - } - }); - } - } else { + if (qb?.client?.config?.client === 'mysql2') { + if ( + [ + UITypes.Duration, + UITypes.Currency, + UITypes.Percent, + UITypes.Number, + UITypes.Decimal, + UITypes.Rollup, + ].includes(column.uidt) + ) { qb = qb.where((nestedQb) => { nestedQb .whereNot(field, val) .orWhereNull(customWhereClause ? _val : _field); }); + } else if (column.uidt === UITypes.Rating) { + // unset rating is considered as NULL + if (val === 0) { + qb = qb.whereNot(field, val).whereNotNull(field); + } else { + qb = qb.whereNot(field, val).orWhereNull(field); + } + } else { + // mysql is case-insensitive for strings, turn to case-sensitive + qb = qb.where((nestedQb) => { + nestedQb.whereRaw('BINARY ?? != ?', [field, val]); + if (column.uidt !== UITypes.Rating) { + nestedQb.orWhereNull(customWhereClause ? _val : _field); + } + }); } + } else { + qb = qb.where((nestedQb) => { + nestedQb + .whereNot(field, val) + .orWhereNull(customWhereClause ? _val : _field); + }); } break; case 'like': @@ -474,59 +461,43 @@ const parseConditionV2 = async ( break; case 'gt': const gt_op = customWhereClause ? '<' : '>'; - if ([UITypes.Date, UITypes.DateTime].includes(column.uidt)) { - parseDateOrDateTimeCondition(qb, gt_op, val); - } else { - qb = qb.where(field, gt_op, val); - if (column.uidt === UITypes.Rating) { - // unset rating is considered as NULL - if (gt_op === '<' && val > 0) { - qb = qb.orWhereNull(field); - } + qb = qb.where(field, gt_op, val); + if (column.uidt === UITypes.Rating) { + // unset rating is considered as NULL + if (gt_op === '<' && val > 0) { + qb = qb.orWhereNull(field); } } break; case 'ge': case 'gte': const ge_op = customWhereClause ? '<=' : '>='; - if ([UITypes.Date, UITypes.DateTime].includes(column.uidt)) { - parseDateOrDateTimeCondition(qb, ge_op, val); - } else { - qb = qb.where(field, ge_op, val); - if (column.uidt === UITypes.Rating) { - // unset rating is considered as NULL - if (ge_op === '<=' || (ge_op === '>=' && val === 0)) { - qb = qb.orWhereNull(field); - } + qb = qb.where(field, ge_op, val); + if (column.uidt === UITypes.Rating) { + // unset rating is considered as NULL + if (ge_op === '<=' || (ge_op === '>=' && val === 0)) { + qb = qb.orWhereNull(field); } } break; case 'lt': const lt_op = customWhereClause ? '>' : '<'; - if ([UITypes.Date, UITypes.DateTime].includes(column.uidt)) { - parseDateOrDateTimeCondition(qb, lt_op, val); - } else { - qb = qb.where(field, lt_op, val); - if (column.uidt === UITypes.Rating) { - // unset number is considered as NULL - if (lt_op === '<' && val > 0) { - qb = qb.orWhereNull(field); - } + qb = qb.where(field, lt_op, val); + if (column.uidt === UITypes.Rating) { + // unset number is considered as NULL + if (lt_op === '<' && val > 0) { + qb = qb.orWhereNull(field); } } break; case 'le': case 'lte': const le_op = customWhereClause ? '>=' : '<='; - if ([UITypes.Date, UITypes.DateTime].includes(column.uidt)) { - parseDateOrDateTimeCondition(qb, le_op, val); - } else { - qb = qb.where(field, le_op, val); - if (column.uidt === UITypes.Rating) { - // unset number is considered as NULL - if (le_op === '<=' || (le_op === '>=' && val === 0)) { - qb = qb.orWhereNull(field); - } + qb = qb.where(field, le_op, val); + if (column.uidt === UITypes.Rating) { + // unset number is considered as NULL + if (le_op === '<=' || (le_op === '>=' && val === 0)) { + qb = qb.orWhereNull(field); } } break; From b7685db541e1bbfdb0d98408b7796e45308601b3 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 23 Feb 2023 16:20:51 +0800 Subject: [PATCH 019/404] feat(nc-gui): add today --- packages/nc-gui/utils/filterUtils.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/nc-gui/utils/filterUtils.ts b/packages/nc-gui/utils/filterUtils.ts index 45f7819543..17f9701602 100644 --- a/packages/nc-gui/utils/filterUtils.ts +++ b/packages/nc-gui/utils/filterUtils.ts @@ -230,6 +230,12 @@ export const comparisonSubOpList: { includedTypes?: UITypes[] excludedTypes?: UITypes[] }[] = [ + { + text: 'today', + value: 'today', + ignoreVal: true, + includedTypes: [UITypes.Date, UITypes.DateTime], + }, { text: 'tomorrow', value: 'tomorrow', From ec1b5ddcfddc4e8ca5944be1fd13d4dcca35c8ca Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 23 Feb 2023 17:11:51 +0800 Subject: [PATCH 020/404] feat(nocodb): revise val for comparison_sub_op --- .../db/sql-data-mapper/lib/sql/conditionV2.ts | 53 +++++++++++++++++-- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts index 68c6c54547..66784fda59 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts @@ -10,6 +10,9 @@ import formulaQueryBuilderv2 from './formulav2/formulaQueryBuilderv2'; import FormulaColumn from '../../../../models/FormulaColumn'; import { RelationTypes, UITypes, isNumericCol } from 'nocodb-sdk'; import { sanitize } from './helpers/sanitize'; +import dayjs, { extend } from 'dayjs'; +import customParseFormat from 'dayjs/plugin/customParseFormat.js'; +extend(customParseFormat); export default async function conditionV2( conditionObj: Filter | Filter[], @@ -272,12 +275,52 @@ const parseConditionV2 = async ( return (qb: Knex.QueryBuilder) => { let [field, val] = [_field, _val]; if ([UITypes.Date, UITypes.DateTime].includes(column.uidt)) { - if (!val) { - // for date & datetime, val cannot be empty for all filters - return; - } + const dateFormat = + qb?.client?.config?.client === 'mysql2' + ? 'YYYY-MM-DD HH:mm:ss' + : 'YYYY-MM-DD HH:mm:ssZ'; + let now = dayjs(new Date()); // handle sub operation - // TODO: + switch (filter.comparison_sub_op) { + case 'today': + val = now; + break; + case 'tomorrow': + val = now.add(1, 'day'); + break; + case 'yesterday': + val = now.add(-1, 'day'); + break; + case 'one_week_ago': + val = now.add(-7, 'day'); + break; + case 'one_week_from_now': + val = now.add(7, 'day'); + break; + case 'one_month_ago': + val = now.add(-1, 'month'); + break; + case 'one_month_from_now': + val = now.add(1, 'month'); + break; + case 'number_of_days_ago': + if (!val) return; + val = now.add(-val, 'day'); + break; + case 'number_of_days_from_now': + if (!val) return; + val = now.add(val, 'day'); + break; + case 'exact_date': + if (!val) return; + break; + } + + if (filter.comparison_sub_op !== 'exact_date') { + // val for exact_date is not a dayjs object + val = val.format(dateFormat).toString(); + val = column.uidt === UITypes.Date ? val.substring(0, 10) : val; + } } if (isNumericCol(column.uidt) && typeof val === 'string') { From c465e661fa777a412f3c1685165d1796308e998c Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 23 Feb 2023 17:19:55 +0800 Subject: [PATCH 021/404] feat(nc-gui): render decimal input for number_of_days_ago', 'number_of_days_from_now --- .../nc-gui/components/smartsheet/toolbar/FilterInput.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/nc-gui/components/smartsheet/toolbar/FilterInput.vue b/packages/nc-gui/components/smartsheet/toolbar/FilterInput.vue index 544132d6e4..a5d31b82e0 100644 --- a/packages/nc-gui/components/smartsheet/toolbar/FilterInput.vue +++ b/packages/nc-gui/components/smartsheet/toolbar/FilterInput.vue @@ -117,9 +117,11 @@ const componentMap: Partial> = $computed(() => { // use MultiSelect for SingleSelect columns for anyof / nanyof filters isSingleSelect: ['anyof', 'nanyof'].includes(props.filter.comparison_op!) ? MultiSelect : SingleSelect, isMultiSelect: MultiSelect, - isDate: DatePicker, + isDate: ['number_of_days_ago', 'number_of_days_from_now'].includes(props.filter.comparison_sub_op!) ? Decimal : DatePicker, isYear: YearPicker, - isDateTime: DateTimePicker, + isDateTime: ['number_of_days_ago', 'number_of_days_from_now'].includes(props.filter.comparison_sub_op!) + ? Decimal + : DateTimePicker, isTime: TimePicker, isRating: Rating, isDuration: Duration, From dd6a8a4ecf1acf9eaa6428a2f990bb3115283843 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 23 Feb 2023 17:31:48 +0800 Subject: [PATCH 022/404] feat(nc-gui): reset filter input value when changing comparison sub operation --- .../nc-gui/components/smartsheet/toolbar/ColumnFilter.vue | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue b/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue index 07e1dabe73..6239e22032 100644 --- a/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue +++ b/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue @@ -90,6 +90,11 @@ const filterUpdateCondition = (filter: FilterType, i: number) => { // since `blank`, `empty`, `null` doesn't require value, // hence remove the previous value filter.value = '' + } else if ([UITypes.Date, UITypes.DateTime].includes(col.uidt as UITypes)) { + // for date / datetime, + // the input type could be decimal or datepicker / datetime picker + // hence remove the previous value + filter.value = '' } saveOrUpdate(filter, i) filterPrevComparisonOp.value[filter.id] = filter.comparison_op From 1441925c0c3c9d8fc6dda11f77f9f0a05417e4c8 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 23 Feb 2023 17:50:02 +0800 Subject: [PATCH 023/404] feat(nocodb): add migrateEqAndNeqFilters --- .../ncFilterUpgrader_0105003.ts | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/nocodb/src/lib/version-upgrader/ncFilterUpgrader_0105003.ts b/packages/nocodb/src/lib/version-upgrader/ncFilterUpgrader_0105003.ts index a9252de580..41aef9946b 100644 --- a/packages/nocodb/src/lib/version-upgrader/ncFilterUpgrader_0105003.ts +++ b/packages/nocodb/src/lib/version-upgrader/ncFilterUpgrader_0105003.ts @@ -6,10 +6,15 @@ import Filter from '../models/Filter'; import { UITypes } from 'nocodb-sdk'; // as of 0.105.3, date / datetime filters include `is like` and `is not like` which are not practical -// this upgrader is simply to remove them +// `removeFilters` in this upgrader is simply to remove them +// since the upcoming version will introduce a set of new filters for date / datetime with a new `comparison_sub_op` +// `eq` and `neq` would become `is` / `is not` (comparison_op) + `exact date` (comparison_sub_op) +// `migrateEqAndNeqFilters` in this upgrader is to add `exact date` in comparison_sub_op // Change Summary: -// - Date / DateTime columns: remove `is like` and `is not like` +// - Date / DateTime columns: +// - remove `is like` and `is not like` +// - add `exact date` in comparison_sub_op for existing filters `eq` and `neq` const removeLikeFilters = (filter, ncMeta) => { let actions = []; @@ -33,6 +38,27 @@ async function removeFilters(ncMeta: NcMetaIO) { } } +async function migrateEqAndNeqFilters(ncMeta: NcMetaIO) { + let actions = []; + const filters = await ncMeta.metaList2(null, null, MetaTable.FILTER_EXP); + for (const filter of filters) { + if ( + !filter.fk_column_id || + filter.is_group || + !['eq', 'neq'].includes(filter.comparison_op) + ) { + continue; + } + actions.push( + Filter.update(filter.id, { + comparison_sub_op: 'exact_date', + }) + ); + } + await Promise.all(actions); +} + export default async function ({ ncMeta }: NcUpgraderCtx) { await removeFilters(ncMeta); + await migrateEqAndNeqFilters(ncMeta); } From dd633ee357f0c42d55b90c9d895db351a2b34650 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 23 Feb 2023 17:52:23 +0800 Subject: [PATCH 024/404] refactor(nocodb): removeLikeAndNlikeFilters --- .../ncFilterUpgrader_0105003.ts | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/packages/nocodb/src/lib/version-upgrader/ncFilterUpgrader_0105003.ts b/packages/nocodb/src/lib/version-upgrader/ncFilterUpgrader_0105003.ts index 41aef9946b..d2fe6f7f81 100644 --- a/packages/nocodb/src/lib/version-upgrader/ncFilterUpgrader_0105003.ts +++ b/packages/nocodb/src/lib/version-upgrader/ncFilterUpgrader_0105003.ts @@ -6,7 +6,8 @@ import Filter from '../models/Filter'; import { UITypes } from 'nocodb-sdk'; // as of 0.105.3, date / datetime filters include `is like` and `is not like` which are not practical -// `removeFilters` in this upgrader is simply to remove them +// `removeLikeAndNlikeFilters` in this upgrader is simply to remove them + // since the upcoming version will introduce a set of new filters for date / datetime with a new `comparison_sub_op` // `eq` and `neq` would become `is` / `is not` (comparison_op) + `exact date` (comparison_sub_op) // `migrateEqAndNeqFilters` in this upgrader is to add `exact date` in comparison_sub_op @@ -16,16 +17,8 @@ import { UITypes } from 'nocodb-sdk'; // - remove `is like` and `is not like` // - add `exact date` in comparison_sub_op for existing filters `eq` and `neq` -const removeLikeFilters = (filter, ncMeta) => { +async function removeLikeAndNlikeFilters(ncMeta: NcMetaIO) { let actions = []; - // remove `is like` and `is not like` - if (['like', 'nlike'].includes(filter.comparison_op)) { - actions.push(Filter.delete(filter.id, ncMeta)); - } - return actions; -}; - -async function removeFilters(ncMeta: NcMetaIO) { const filters = await ncMeta.metaList2(null, null, MetaTable.FILTER_EXP); for (const filter of filters) { if (!filter.fk_column_id || filter.is_group) { @@ -33,9 +26,13 @@ async function removeFilters(ncMeta: NcMetaIO) { } const col = await Column.get({ colId: filter.fk_column_id }, ncMeta); if ([UITypes.Date, UITypes.DateTime].includes(col.uidt)) { - await Promise.all(removeLikeFilters(filter, ncMeta)); + // remove `is like` and `is not like` + if (['like', 'nlike'].includes(filter.comparison_op)) { + actions.push(Filter.delete(filter.id, ncMeta)); + } } } + await Promise.all(actions); } async function migrateEqAndNeqFilters(ncMeta: NcMetaIO) { @@ -59,6 +56,6 @@ async function migrateEqAndNeqFilters(ncMeta: NcMetaIO) { } export default async function ({ ncMeta }: NcUpgraderCtx) { - await removeFilters(ncMeta); + await removeLikeAndNlikeFilters(ncMeta); await migrateEqAndNeqFilters(ncMeta); } From b0fbf97e078bdf70054fb8d349af9645dec2a908 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Thu, 23 Feb 2023 17:52:43 +0800 Subject: [PATCH 025/404] fix(nocodb): add missing ncMeta in migrateEqAndNeqFilters --- .../lib/version-upgrader/ncFilterUpgrader_0105003.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/nocodb/src/lib/version-upgrader/ncFilterUpgrader_0105003.ts b/packages/nocodb/src/lib/version-upgrader/ncFilterUpgrader_0105003.ts index d2fe6f7f81..8ddf5f6b48 100644 --- a/packages/nocodb/src/lib/version-upgrader/ncFilterUpgrader_0105003.ts +++ b/packages/nocodb/src/lib/version-upgrader/ncFilterUpgrader_0105003.ts @@ -47,9 +47,13 @@ async function migrateEqAndNeqFilters(ncMeta: NcMetaIO) { continue; } actions.push( - Filter.update(filter.id, { - comparison_sub_op: 'exact_date', - }) + Filter.update( + filter.id, + { + comparison_sub_op: 'exact_date', + }, + ncMeta + ) ); } await Promise.all(actions); From b77d357e8c339f7cbda4a55578e95a9e061662db Mon Sep 17 00:00:00 2001 From: Raju Udava <86527202+dstala@users.noreply.github.com> Date: Thu, 23 Feb 2023 17:15:10 +0530 Subject: [PATCH 026/404] test: date tests, disabled Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> --- .../pages/Dashboard/common/Toolbar/Filter.ts | 45 ++---- tests/playwright/setup/xcdb-records.ts | 6 +- tests/playwright/tests/filters.spec.ts | 141 ++++++++++++++++++ 3 files changed, 162 insertions(+), 30 deletions(-) diff --git a/tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts b/tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts index 8b8420f00a..896987d26d 100644 --- a/tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts +++ b/tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts @@ -32,12 +32,14 @@ export class ToolbarFilterPage extends BasePage { async add({ columnTitle, opType, + opSubType, value, isLocallySaved, dataType, }: { columnTitle: string; opType: string; + opSubType?: string; // for date datatype value?: string; isLocallySaved: boolean; dataType?: string; @@ -53,19 +55,6 @@ export class ToolbarFilterPage extends BasePage { .click(); } - // network request will be triggered only after filter value is configured - // - // const selectColumn = this.rootPage - // .locator('div.ant-select-dropdown.nc-dropdown-toolbar-field-list') - // .locator(`div[label="${columnTitle}"]`) - // .click(); - // await this.waitForResponse({ - // uiAction: selectColumn, - // httpMethodsToMatch: isLocallySaved ? ['GET'] : ['POST', 'PATCH'], - // requestUrlPathToMatch: isLocallySaved ? `/api/v1/db/public/` : `/filters`, - // }); - // await this.toolbar.parent.dashboard.waitForLoaderToDisappear(); - const selectedOpType = await this.rootPage.locator('.nc-filter-operation-select').textContent(); if (selectedOpType !== opType) { await this.rootPage.locator('.nc-filter-operation-select').click(); @@ -76,22 +65,20 @@ export class ToolbarFilterPage extends BasePage { .first() .click(); } - // if (selectedOpType !== opType) { - // await this.rootPage.locator('.nc-filter-operation-select').last().click(); - // // first() : filter list has >, >= - // const selectOpType = this.rootPage - // .locator('.nc-dropdown-filter-comp-op') - // .locator(`.ant-select-item:has-text("${opType}")`) - // .first() - // .click(); - // - // await this.waitForResponse({ - // uiAction: selectOpType, - // httpMethodsToMatch: isLocallySaved ? ['GET'] : ['POST', 'PATCH'], - // requestUrlPathToMatch: isLocallySaved ? `/api/v1/db/public/` : `/filters`, - // }); - // await this.toolbar.parent.dashboard.waitForLoaderToDisappear(); - // } + + // subtype for date + if (dataType === UITypes.Date && opSubType) { + const selectedSubType = await this.rootPage.locator('.nc-filter-sub_operation-select').textContent(); + if (selectedSubType !== opSubType) { + await this.rootPage.locator('.nc-filter-sub_operation-select').click(); + // first() : filter list has >, >= + await this.rootPage + .locator('.nc-dropdown-filter-comp-sub-op') + .locator(`.ant-select-item:has-text("${opSubType}")`) + .first() + .click(); + } + } // if value field was provided, fill it if (value) { diff --git a/tests/playwright/setup/xcdb-records.ts b/tests/playwright/setup/xcdb-records.ts index 4edd3df393..419e4dc442 100644 --- a/tests/playwright/setup/xcdb-records.ts +++ b/tests/playwright/setup/xcdb-records.ts @@ -120,7 +120,11 @@ const rowMixedValue = (column: ColumnType, index: number) => { case UITypes.LongText: return longText[index % longText.length]; case UITypes.Date: - return '2020-01-01'; + // set startDate as 400 days before today + // eslint-disable-next-line no-case-declarations + const result = new Date(); + result.setDate(result.getDate() - 400 + index); + return result; case UITypes.URL: return urls[index % urls.length]; case UITypes.SingleSelect: diff --git a/tests/playwright/tests/filters.spec.ts b/tests/playwright/tests/filters.spec.ts index c50ae29ac6..bccc04e4bd 100644 --- a/tests/playwright/tests/filters.spec.ts +++ b/tests/playwright/tests/filters.spec.ts @@ -47,6 +47,7 @@ async function validateRowArray(param) { async function verifyFilter(param: { column: string; opType: string; + opSubType?: string; value?: string; result: { rowCount: number }; dataType?: string; @@ -60,6 +61,7 @@ async function verifyFilter(param: { await toolbar.filter.add({ columnTitle: param.column, opType: param.opType, + opSubType: param.opSubType, value: param.value, isLocallySaved: false, dataType: param?.dataType, @@ -591,6 +593,145 @@ test.describe('Filter Tests: Select based', () => { }); }); +// Date & Time related +// + +test.describe.skip('Filter Tests: Date & Time related', () => { + async function dateTimeBasedFilterTest(dataType) { + await dashboard.closeTab({ title: 'Team & Auth' }); + await dashboard.treeView.openTable({ title: 'dateTimeBased' }); + + // Enable NULL & EMPTY filters + await dashboard.gotoSettings(); + await dashboard.settings.toggleNullEmptyFilters(); + + // store date in YYYY-MM-DD format + const today = new Date().toISOString().split('T')[0]; + const tomorrow = new Date(new Date().setDate(new Date().getDate() + 1)).toISOString().split('T')[0]; + const yesterday = new Date(new Date().setDate(new Date().getDate() - 1)).toISOString().split('T')[0]; + const oneWeekAgo = new Date(new Date().setDate(new Date().getDate() - 7)).toISOString().split('T')[0]; + const oneWeekFromNow = new Date(new Date().setDate(new Date().getDate() + 7)).toISOString().split('T')[0]; + const oneMonthAgo = new Date(new Date().setMonth(new Date().getMonth() - 1)).toISOString().split('T')[0]; + const oneMonthFromNow = new Date(new Date().setMonth(new Date().getMonth() + 1)).toISOString().split('T')[0]; + const daysAgo45 = new Date(new Date().setDate(new Date().getDate() + 45)).toISOString().split('T')[0]; + const daysFromNow45 = new Date(new Date().setDate(new Date().getDate() - 45)).toISOString().split('T')[0]; + + console.log(today); + + const filterList = [ + { + op: 'is', + opSub: 'today', + rowCount: records.list.filter(r => r[dataType].split('T')[0] === today).length, + }, + { + op: 'is', + opSub: 'tomorrow', + rowCount: records.list.filter(r => r[dataType].split('T')[0] === tomorrow).length, + }, + { + op: 'is', + opSub: 'yesterday', + rowCount: records.list.filter(r => r[dataType].split('T')[0] === yesterday).length, + }, + { + op: 'is', + opSub: 'one week ago', + rowCount: records.list.filter(r => r[dataType].split('T')[0] === oneWeekAgo).length, + }, + { + op: 'is', + opSub: 'one week from now', + rowCount: records.list.filter(r => r[dataType].split('T')[0] === oneWeekFromNow).length, + }, + { + op: 'is', + opSub: 'one month ago', + rowCount: records.list.filter(r => r[dataType].split('T')[0] === oneMonthAgo).length, + }, + { + op: 'is', + opSub: 'one month from now', + rowCount: records.list.filter(r => r[dataType].split('T')[0] === oneMonthFromNow).length, + }, + { + op: 'is', + opSub: 'number of days ago', + value: 45, + rowCount: records.list.filter(r => r[dataType].split('T')[0] === daysAgo45).length, + }, + { + op: 'is', + opSub: 'number of days from now', + value: 45, + rowCount: records.list.filter(r => r[dataType].split('T')[0] === daysFromNow45).length, + }, + ]; + + for (let i = 0; i < filterList.length; i++) { + await verifyFilter({ + column: dataType, + opType: filterList[i].op, + opSubType: filterList[i].opSub, + value: filterList[i]?.value?.toString() || '', + result: { rowCount: filterList[i].rowCount }, + dataType: dataType, + }); + } + } + test.beforeEach(async ({ page }) => { + context = await setup({ page }); + dashboard = new DashboardPage(page, context.project); + toolbar = dashboard.grid.toolbar; + + api = new Api({ + baseURL: `http://localhost:8080/`, + headers: { + 'xc-auth': context.token, + }, + }); + + const columns = [ + { + column_name: 'Id', + title: 'Id', + uidt: UITypes.ID, + }, + { + column_name: 'Date', + title: 'Date', + uidt: UITypes.Date, + }, + ]; + + try { + const project = await api.project.read(context.project.id); + const table = await api.base.tableCreate(context.project.id, project.bases?.[0].id, { + table_name: 'dateTimeBased', + title: 'dateTimeBased', + columns: columns, + }); + + const rowAttributes = []; + for (let i = 0; i < 800; i++) { + const row = { + Date: rowMixedValue(columns[1], i), + }; + rowAttributes.push(row); + } + + await api.dbTableRow.bulkCreate('noco', context.project.id, table.id, rowAttributes); + records = await api.dbTableRow.list('noco', context.project.id, table.id, { limit: 800 }); + } catch (e) { + console.error(e); + } + }); + + test('Date', async () => { + await dateTimeBasedFilterTest('Date'); + }); +}); + // Misc : Checkbox // From 8c0e66311a81083de5babff565d922fe2c51cb6a Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 24 Feb 2023 13:43:24 +0800 Subject: [PATCH 027/404] feat(nocodb): bump to 0105003 --- packages/nocodb/src/lib/Noco.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nocodb/src/lib/Noco.ts b/packages/nocodb/src/lib/Noco.ts index b66287a446..9099de28b4 100644 --- a/packages/nocodb/src/lib/Noco.ts +++ b/packages/nocodb/src/lib/Noco.ts @@ -105,7 +105,7 @@ export default class Noco { constructor() { process.env.PORT = process.env.PORT || '8080'; // todo: move - process.env.NC_VERSION = '0105002'; + process.env.NC_VERSION = '0105003'; // if env variable NC_MINIMAL_DBS is set, then disable project creation with external sources if (process.env.NC_MINIMAL_DBS) { From 7a3b312e15aa0237022462006403a6d357c42562 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 24 Feb 2023 16:12:30 +0800 Subject: [PATCH 028/404] fix(nc-gui): revise display comparison_sub_op logic --- packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue b/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue index 6239e22032..d4232ca929 100644 --- a/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue +++ b/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue @@ -90,11 +90,13 @@ const filterUpdateCondition = (filter: FilterType, i: number) => { // since `blank`, `empty`, `null` doesn't require value, // hence remove the previous value filter.value = '' + filter.comparison_sub_op = null } else if ([UITypes.Date, UITypes.DateTime].includes(col.uidt as UITypes)) { // for date / datetime, // the input type could be decimal or datepicker / datetime picker // hence remove the previous value filter.value = '' + if (!filter.comparison_sub_op) filter.comparison_sub_op = 'exact_date' } saveOrUpdate(filter, i) filterPrevComparisonOp.value[filter.id] = filter.comparison_op From 8de8003ecac11b805e3fd5db6504f1819d315750 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 24 Feb 2023 16:12:56 +0800 Subject: [PATCH 029/404] fix(nocodb): format date string if comparison_sub_op is not null --- .../nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts index 66784fda59..dd5bff9424 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts @@ -316,7 +316,10 @@ const parseConditionV2 = async ( break; } - if (filter.comparison_sub_op !== 'exact_date') { + if ( + filter.comparison_sub_op && + filter.comparison_sub_op !== 'exact_date' + ) { // val for exact_date is not a dayjs object val = val.format(dateFormat).toString(); val = column.uidt === UITypes.Date ? val.substring(0, 10) : val; From a43f6be47cc11062c633b1d043d6c309db652b64 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 24 Feb 2023 16:50:57 +0800 Subject: [PATCH 030/404] refactor(nocodb): rename date filter sub ops --- .../db/sql-data-mapper/lib/sql/conditionV2.ts | 18 +++++++++--------- packages/nocodb/src/lib/models/Filter.ts | 14 +++++++------- .../ncFilterUpgrader_0105003.ts | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts index dd5bff9424..cc1d5f97ea 100644 --- a/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts +++ b/packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts @@ -291,36 +291,36 @@ const parseConditionV2 = async ( case 'yesterday': val = now.add(-1, 'day'); break; - case 'one_week_ago': + case 'oneWeekAgo': val = now.add(-7, 'day'); break; - case 'one_week_from_now': + case 'oneWeekFromNow': val = now.add(7, 'day'); break; - case 'one_month_ago': + case 'oneMonthAgo': val = now.add(-1, 'month'); break; - case 'one_month_from_now': + case 'oneMonthFromNow': val = now.add(1, 'month'); break; - case 'number_of_days_ago': + case 'daysAgo': if (!val) return; val = now.add(-val, 'day'); break; - case 'number_of_days_from_now': + case 'daysFromNow': if (!val) return; val = now.add(val, 'day'); break; - case 'exact_date': + case 'exactDate': if (!val) return; break; } if ( filter.comparison_sub_op && - filter.comparison_sub_op !== 'exact_date' + filter.comparison_sub_op !== 'exactDate' ) { - // val for exact_date is not a dayjs object + // val for exactDate is not a dayjs object val = val.format(dateFormat).toString(); val = column.uidt === UITypes.Date ? val.substring(0, 10) : val; } diff --git a/packages/nocodb/src/lib/models/Filter.ts b/packages/nocodb/src/lib/models/Filter.ts index 1267e2a5e9..f083d9f3e7 100644 --- a/packages/nocodb/src/lib/models/Filter.ts +++ b/packages/nocodb/src/lib/models/Filter.ts @@ -57,13 +57,13 @@ export default class Filter { | 'today' | 'tomorrow' | 'yesterday' - | 'one_week_ago' - | 'one_week_from_now' - | 'one_month_ago' - | 'one_month_from_now' - | 'number_of_days_ago' - | 'number_of_days_from_now' - | 'exact_date'; + | 'oneWeekAgo' + | 'oneWeekFromNow' + | 'oneMonthAgo' + | 'oneMonthFromNow' + | 'daysAgo' + | 'daysFromNow' + | 'exactDate'; value?: string; diff --git a/packages/nocodb/src/lib/version-upgrader/ncFilterUpgrader_0105003.ts b/packages/nocodb/src/lib/version-upgrader/ncFilterUpgrader_0105003.ts index 8ddf5f6b48..61caba1fc1 100644 --- a/packages/nocodb/src/lib/version-upgrader/ncFilterUpgrader_0105003.ts +++ b/packages/nocodb/src/lib/version-upgrader/ncFilterUpgrader_0105003.ts @@ -50,7 +50,7 @@ async function migrateEqAndNeqFilters(ncMeta: NcMetaIO) { Filter.update( filter.id, { - comparison_sub_op: 'exact_date', + comparison_sub_op: 'exactDate', }, ncMeta ) From 445fb6cdcd6ceb564bc088ea286fc2819757a108 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 24 Feb 2023 16:51:08 +0800 Subject: [PATCH 031/404] refactor(nc-gui): rename date filter sub ops --- .../components/smartsheet/toolbar/ColumnFilter.vue | 4 ++-- .../components/smartsheet/toolbar/FilterInput.vue | 4 ++-- packages/nc-gui/utils/filterUtils.ts | 14 +++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue b/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue index d4232ca929..f2710bdc4c 100644 --- a/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue +++ b/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue @@ -96,7 +96,7 @@ const filterUpdateCondition = (filter: FilterType, i: number) => { // the input type could be decimal or datepicker / datetime picker // hence remove the previous value filter.value = '' - if (!filter.comparison_sub_op) filter.comparison_sub_op = 'exact_date' + if (!filter.comparison_sub_op) filter.comparison_sub_op = 'exactDate' } saveOrUpdate(filter, i) filterPrevComparisonOp.value[filter.id] = filter.comparison_op @@ -160,7 +160,7 @@ const selectFilterField = (filter: Filter, index: number) => { )?.[0].value if ([UITypes.Date, UITypes.DateTime].includes(col.uidt as UITypes) && !['blank', 'notblank'].includes(filter.comparison_op)) { - filter.comparison_sub_op = 'exact_date' + filter.comparison_sub_op = 'exactDate' } else { // reset filter.comparison_sub_op = '' diff --git a/packages/nc-gui/components/smartsheet/toolbar/FilterInput.vue b/packages/nc-gui/components/smartsheet/toolbar/FilterInput.vue index a5d31b82e0..5c18353e49 100644 --- a/packages/nc-gui/components/smartsheet/toolbar/FilterInput.vue +++ b/packages/nc-gui/components/smartsheet/toolbar/FilterInput.vue @@ -117,9 +117,9 @@ const componentMap: Partial> = $computed(() => { // use MultiSelect for SingleSelect columns for anyof / nanyof filters isSingleSelect: ['anyof', 'nanyof'].includes(props.filter.comparison_op!) ? MultiSelect : SingleSelect, isMultiSelect: MultiSelect, - isDate: ['number_of_days_ago', 'number_of_days_from_now'].includes(props.filter.comparison_sub_op!) ? Decimal : DatePicker, + isDate: ['daysAgo', 'daysFromNow'].includes(props.filter.comparison_sub_op!) ? Decimal : DatePicker, isYear: YearPicker, - isDateTime: ['number_of_days_ago', 'number_of_days_from_now'].includes(props.filter.comparison_sub_op!) + isDateTime: ['daysAgo', 'daysFromNow'].includes(props.filter.comparison_sub_op!) ? Decimal : DateTimePicker, isTime: TimePicker, diff --git a/packages/nc-gui/utils/filterUtils.ts b/packages/nc-gui/utils/filterUtils.ts index 17f9701602..756736063c 100644 --- a/packages/nc-gui/utils/filterUtils.ts +++ b/packages/nc-gui/utils/filterUtils.ts @@ -250,43 +250,43 @@ export const comparisonSubOpList: { }, { text: 'one week ago', - value: 'one_week_ago', + value: 'oneWeekAgo', ignoreVal: true, includedTypes: [UITypes.Date, UITypes.DateTime], }, { text: 'one week from now', - value: 'one_week_from_now', + value: 'oneWeekFromNow', ignoreVal: true, includedTypes: [UITypes.Date, UITypes.DateTime], }, { text: 'one month ago', - value: 'one_month_ago', + value: 'oneMonthAgo', ignoreVal: true, includedTypes: [UITypes.Date, UITypes.DateTime], }, { text: 'one month from now', - value: 'one_month_from_now', + value: 'oneMonthFromNow', ignoreVal: true, includedTypes: [UITypes.Date, UITypes.DateTime], }, { text: 'number of days ago', - value: 'number_of_days_ago', + value: 'daysAgo', ignoreVal: false, includedTypes: [UITypes.Date, UITypes.DateTime], }, { text: 'number of days from now', - value: 'number_of_days_from_now', + value: 'daysFromNow', ignoreVal: false, includedTypes: [UITypes.Date, UITypes.DateTime], }, { text: 'exact date', - value: 'exact_date', + value: 'exactDate', ignoreVal: false, includedTypes: [UITypes.Date, UITypes.DateTime], }, From ac57a133d37c7d694fa7c55768d93569558fdb0e Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 24 Feb 2023 17:28:03 +0800 Subject: [PATCH 032/404] feat(nocodb): airtable migration for date/datetime filters --- .../src/lib/meta/api/sync/helpers/job.ts | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/packages/nocodb/src/lib/meta/api/sync/helpers/job.ts b/packages/nocodb/src/lib/meta/api/sync/helpers/job.ts index 80820cfeaf..06ba6875df 100644 --- a/packages/nocodb/src/lib/meta/api/sync/helpers/job.ts +++ b/packages/nocodb/src/lib/meta/api/sync/helpers/job.ts @@ -679,8 +679,6 @@ export default async ( ); } - // debug - // console.log(JSON.stringify(tables, null, 2)); return tables; } @@ -914,8 +912,6 @@ export default async ( aTblLinkColumns[i].name + suffix, ncTbl.id ); - - // console.log(res.columns.find(x => x.title === aTblLinkColumns[i].name)) } } } @@ -1504,8 +1500,6 @@ export default async ( }) .eachPage( async function page(records, fetchNextPage) { - // console.log(JSON.stringify(records, null, 2)); - // This function (`page`) will get called for each page of records. // records.forEach(record => callback(table, record)); logBasic( @@ -2002,16 +1996,31 @@ export default async ( const datatype = colSchema.uidt; const ncFilters = []; - // console.log(filter) if (datatype === UITypes.Date || datatype === UITypes.DateTime) { - // skip filters over data datatype - updateMigrationSkipLog( - await sMap.getNcNameFromAtId(viewId), - colSchema.title, - colSchema.uidt, - `filter config skipped; filter over date datatype not supported` - ); - continue; + if (['isEmpty', 'isNotEmpty'].includes(filter.operator)) { + const fx = { + fk_column_id: columnId, + logical_op: f.conjunction, + comparison_op: filter.operator === 'isEmpty' ? 'blank' : 'notblank', + value: null, + }; + ncFilters.push(fx); + } else { + let value = null; + if ('numberOfDays' in filter.value) { + value = filter.value['numberOfDays']; + } else if ('exactDate' in filter.value) { + value = filter.value['exactDate']; + } + const fx = { + fk_column_id: columnId, + logical_op: f.conjunction, + comparison_op: filterMap[filter.operator], + comparison_sub_op: filter.value.mode, + value, + }; + ncFilters.push(fx); + } } // single-select & multi-select From deb057b7609be8dc175f809f81cac8c140c2471d Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 24 Feb 2023 17:29:29 +0800 Subject: [PATCH 033/404] feat(nc-gui): add empty span as a grid column --- packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue b/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue index f2710bdc4c..8d5260d0d5 100644 --- a/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue +++ b/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue @@ -281,6 +281,7 @@ defineExpose({ + Date: Fri, 24 Feb 2023 18:49:48 +0800 Subject: [PATCH 034/404] feat(nc-gui): add is within and mark ignoreVal compulsory --- packages/nc-gui/utils/filterUtils.ts | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/nc-gui/utils/filterUtils.ts b/packages/nc-gui/utils/filterUtils.ts index 756736063c..f979d90c61 100644 --- a/packages/nc-gui/utils/filterUtils.ts +++ b/packages/nc-gui/utils/filterUtils.ts @@ -73,7 +73,7 @@ export const comparisonOpList = ( ): { text: string value: string - ignoreVal?: boolean + ignoreVal: boolean includedTypes?: UITypes[] excludedTypes?: UITypes[] }[] => [ @@ -92,21 +92,25 @@ export const comparisonOpList = ( { text: getEqText(fieldUiType), value: 'eq', + ignoreVal: false, excludedTypes: [UITypes.Checkbox, UITypes.MultiSelect, UITypes.Attachment], }, { text: getNeqText(fieldUiType), value: 'neq', + ignoreVal: false, excludedTypes: [UITypes.Checkbox, UITypes.MultiSelect, UITypes.Attachment], }, { text: getLikeText(fieldUiType), value: 'like', + ignoreVal: false, excludedTypes: [UITypes.Checkbox, UITypes.SingleSelect, UITypes.MultiSelect, UITypes.Collaborator, ...numericUITypes], }, { text: getNotLikeText(fieldUiType), value: 'nlike', + ignoreVal: false, excludedTypes: [UITypes.Checkbox, UITypes.SingleSelect, UITypes.MultiSelect, UITypes.Collaborator, ...numericUITypes], }, { @@ -172,43 +176,57 @@ export const comparisonOpList = ( { text: 'contains all of', value: 'allof', + ignoreVal: false, includedTypes: [UITypes.MultiSelect], }, { text: 'contains any of', value: 'anyof', + ignoreVal: false, includedTypes: [UITypes.MultiSelect, UITypes.SingleSelect], }, { text: 'does not contain all of', value: 'nallof', + ignoreVal: false, includedTypes: [UITypes.MultiSelect], }, { text: 'does not contain any of', value: 'nanyof', + ignoreVal: false, includedTypes: [UITypes.MultiSelect, UITypes.SingleSelect], }, { text: getGtText(fieldUiType), value: 'gt', + ignoreVal: false, includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime], }, { text: getLtText(fieldUiType), value: 'lt', + ignoreVal: false, includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime], }, { text: getGteText(fieldUiType), value: 'gte', + ignoreVal: false, includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime], }, { text: getLteText(fieldUiType), value: 'lte', + ignoreVal: false, includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime], }, + { + text: 'is within', + value: 'isWithin', + ignoreVal: true, + includedTypes: [UITypes.Date, UITypes.DateTime], + }, { text: 'is blank', value: 'blank', @@ -226,7 +244,7 @@ export const comparisonOpList = ( export const comparisonSubOpList: { text: string value: string - ignoreVal?: boolean + ignoreVal: boolean includedTypes?: UITypes[] excludedTypes?: UITypes[] }[] = [ From 3443a81a00b424c0fea65774956b92cf11546405 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 24 Feb 2023 18:49:56 +0800 Subject: [PATCH 035/404] feat(nocodb): add isWithin --- packages/nocodb/src/lib/models/Filter.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/nocodb/src/lib/models/Filter.ts b/packages/nocodb/src/lib/models/Filter.ts index f083d9f3e7..a716040550 100644 --- a/packages/nocodb/src/lib/models/Filter.ts +++ b/packages/nocodb/src/lib/models/Filter.ts @@ -50,6 +50,7 @@ export default class Filter { | 'in' | 'isnot' | 'is' + | 'isWithin' | 'btw' | 'nbtw'; From 9d04cf769240ebd9a67b0ff1d3c1095bc1f0d6cf Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 24 Feb 2023 19:16:54 +0800 Subject: [PATCH 036/404] feat(nocodb): isWithin filter migration --- .../src/lib/meta/api/sync/helpers/job.ts | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/packages/nocodb/src/lib/meta/api/sync/helpers/job.ts b/packages/nocodb/src/lib/meta/api/sync/helpers/job.ts index 06ba6875df..f50a48e2ab 100644 --- a/packages/nocodb/src/lib/meta/api/sync/helpers/job.ts +++ b/packages/nocodb/src/lib/meta/api/sync/helpers/job.ts @@ -1968,6 +1968,7 @@ export default async ( '>=': 'gte', isEmpty: 'empty', isNotEmpty: 'notempty', + isWithin: 'isWithin', contains: 'like', doesNotContain: 'nlike', isAnyOf: 'anyof', @@ -2005,6 +2006,47 @@ export default async ( value: null, }; ncFilters.push(fx); + } else if (filter.operator === 'isWithin') { + let comparison_sub_op = null; + let value = null; + switch (filter.value.mode) { + case 'pastWeek': + comparison_sub_op = 'oneWeekAgo'; + break; + case 'pastMonth': + comparison_sub_op = 'oneMonthAgo'; + break; + case 'pastYear': + comparison_sub_op = 'daysAgo'; + value = 365; + break; + case 'nextWeek': + comparison_sub_op = 'oneWeekFromNow'; + break; + case 'nextMonth': + comparison_sub_op = 'oneMonthFromNow'; + break; + case 'nextYear': + comparison_sub_op = 'daysFromNow'; + value = 365; + break; + case 'pastNumberOfDays': + comparison_sub_op = 'daysAgo'; + value = filter.value.numberOfDays; + break; + case 'nextNumberOfDays': + comparison_sub_op = 'daysFromNow'; + value = filter.value.numberOfDays; + break; + } + const fx = { + fk_column_id: columnId, + logical_op: f.conjunction, + comparison_op: filter.operator, + comparison_sub_op, + value, + }; + ncFilters.push(fx); } else { let value = null; if ('numberOfDays' in filter.value) { From 993660638f310fbe8cf2c5b98d6bd1bad6eee164 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Fri, 24 Feb 2023 19:32:10 +0800 Subject: [PATCH 037/404] refactor(nocodb): move duplicate logic out --- .../src/lib/meta/api/sync/helpers/job.ts | 41 +++++++------------ 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/packages/nocodb/src/lib/meta/api/sync/helpers/job.ts b/packages/nocodb/src/lib/meta/api/sync/helpers/job.ts index f50a48e2ab..e41185fbcd 100644 --- a/packages/nocodb/src/lib/meta/api/sync/helpers/job.ts +++ b/packages/nocodb/src/lib/meta/api/sync/helpers/job.ts @@ -1998,17 +1998,12 @@ export default async ( const ncFilters = []; if (datatype === UITypes.Date || datatype === UITypes.DateTime) { + let comparison_op = null; + let comparison_sub_op = null; + let value = null; if (['isEmpty', 'isNotEmpty'].includes(filter.operator)) { - const fx = { - fk_column_id: columnId, - logical_op: f.conjunction, - comparison_op: filter.operator === 'isEmpty' ? 'blank' : 'notblank', - value: null, - }; - ncFilters.push(fx); + comparison_op = filter.operator === 'isEmpty' ? 'blank' : 'notblank'; } else if (filter.operator === 'isWithin') { - let comparison_sub_op = null; - let value = null; switch (filter.value.mode) { case 'pastWeek': comparison_sub_op = 'oneWeekAgo'; @@ -2039,30 +2034,24 @@ export default async ( value = filter.value.numberOfDays; break; } - const fx = { - fk_column_id: columnId, - logical_op: f.conjunction, - comparison_op: filter.operator, - comparison_sub_op, - value, - }; - ncFilters.push(fx); + comparison_op = filter.operator; } else { - let value = null; if ('numberOfDays' in filter.value) { value = filter.value['numberOfDays']; } else if ('exactDate' in filter.value) { value = filter.value['exactDate']; } - const fx = { - fk_column_id: columnId, - logical_op: f.conjunction, - comparison_op: filterMap[filter.operator], - comparison_sub_op: filter.value.mode, - value, - }; - ncFilters.push(fx); + comparison_op = filterMap[filter.operator]; + comparison_sub_op = filter.value.mode; } + const fx = { + fk_column_id: columnId, + logical_op: f.conjunction, + comparison_op, + comparison_sub_op, + value, + }; + ncFilters.push(fx); } // single-select & multi-select From 811fef9678d75865e4f1a952235659104ae75a03 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 25 Feb 2023 12:43:47 +0800 Subject: [PATCH 038/404] feat(nc-gui): include isWithin sub ops in comparisonSubOpList --- packages/nc-gui/utils/filterUtils.ts | 183 ++++++++++++++++++--------- 1 file changed, 120 insertions(+), 63 deletions(-) diff --git a/packages/nc-gui/utils/filterUtils.ts b/packages/nc-gui/utils/filterUtils.ts index f979d90c61..8f8161af6f 100644 --- a/packages/nc-gui/utils/filterUtils.ts +++ b/packages/nc-gui/utils/filterUtils.ts @@ -241,71 +241,128 @@ export const comparisonOpList = ( }, ] -export const comparisonSubOpList: { +export const comparisonSubOpList = ( + // TODO: type + comparison_op: string, +): { text: string value: string ignoreVal: boolean includedTypes?: UITypes[] excludedTypes?: UITypes[] -}[] = [ - { - text: 'today', - value: 'today', - ignoreVal: true, - includedTypes: [UITypes.Date, UITypes.DateTime], - }, - { - text: 'tomorrow', - value: 'tomorrow', - ignoreVal: true, - includedTypes: [UITypes.Date, UITypes.DateTime], - }, - { - text: 'yesterday', - value: 'yesterday', - ignoreVal: true, - includedTypes: [UITypes.Date, UITypes.DateTime], - }, - { - text: 'one week ago', - value: 'oneWeekAgo', - ignoreVal: true, - includedTypes: [UITypes.Date, UITypes.DateTime], - }, - { - text: 'one week from now', - value: 'oneWeekFromNow', - ignoreVal: true, - includedTypes: [UITypes.Date, UITypes.DateTime], - }, - { - text: 'one month ago', - value: 'oneMonthAgo', - ignoreVal: true, - includedTypes: [UITypes.Date, UITypes.DateTime], - }, - { - text: 'one month from now', - value: 'oneMonthFromNow', - ignoreVal: true, - includedTypes: [UITypes.Date, UITypes.DateTime], - }, - { - text: 'number of days ago', - value: 'daysAgo', - ignoreVal: false, - includedTypes: [UITypes.Date, UITypes.DateTime], - }, - { - text: 'number of days from now', - value: 'daysFromNow', - ignoreVal: false, - includedTypes: [UITypes.Date, UITypes.DateTime], - }, - { - text: 'exact date', - value: 'exactDate', - ignoreVal: false, - includedTypes: [UITypes.Date, UITypes.DateTime], - }, -] +}[] => { + if (comparison_op === 'isWithin') { + return [ + { + text: 'the past week', + value: 'pastWeek', + ignoreVal: true, + includedTypes: [UITypes.Date, UITypes.DateTime], + }, + { + text: 'the past month', + value: 'pastMonth', + ignoreVal: true, + includedTypes: [UITypes.Date, UITypes.DateTime], + }, + { + text: 'the past year', + value: 'pastYear', + ignoreVal: true, + includedTypes: [UITypes.Date, UITypes.DateTime], + }, + { + text: 'the next week', + value: 'nextWeek', + ignoreVal: true, + includedTypes: [UITypes.Date, UITypes.DateTime], + }, + { + text: 'the next month', + value: 'nextMonth', + ignoreVal: true, + includedTypes: [UITypes.Date, UITypes.DateTime], + }, + { + text: 'the next year', + value: 'nextYear', + ignoreVal: true, + includedTypes: [UITypes.Date, UITypes.DateTime], + }, + { + text: 'the next number of days', + value: 'pastNumberOfDays', + ignoreVal: false, + includedTypes: [UITypes.Date, UITypes.DateTime], + }, + { + text: 'the past number of days', + value: 'nextNumberOfDays', + ignoreVal: false, + includedTypes: [UITypes.Date, UITypes.DateTime], + }, + ] + } + return [ + { + text: 'today', + value: 'today', + ignoreVal: true, + includedTypes: [UITypes.Date, UITypes.DateTime], + }, + { + text: 'tomorrow', + value: 'tomorrow', + ignoreVal: true, + includedTypes: [UITypes.Date, UITypes.DateTime], + }, + { + text: 'yesterday', + value: 'yesterday', + ignoreVal: true, + includedTypes: [UITypes.Date, UITypes.DateTime], + }, + { + text: 'one week ago', + value: 'oneWeekAgo', + ignoreVal: true, + includedTypes: [UITypes.Date, UITypes.DateTime], + }, + { + text: 'one week from now', + value: 'oneWeekFromNow', + ignoreVal: true, + includedTypes: [UITypes.Date, UITypes.DateTime], + }, + { + text: 'one month ago', + value: 'oneMonthAgo', + ignoreVal: true, + includedTypes: [UITypes.Date, UITypes.DateTime], + }, + { + text: 'one month from now', + value: 'oneMonthFromNow', + ignoreVal: true, + includedTypes: [UITypes.Date, UITypes.DateTime], + }, + { + text: 'number of days ago', + value: 'daysAgo', + ignoreVal: false, + includedTypes: [UITypes.Date, UITypes.DateTime], + }, + { + text: 'number of days from now', + value: 'daysFromNow', + ignoreVal: false, + includedTypes: [UITypes.Date, UITypes.DateTime], + }, + { + text: 'exact date', + value: 'exactDate', + ignoreVal: false, + includedTypes: [UITypes.Date, UITypes.DateTime], + }, + ] +} From 12973aaf981466afdfa75da541c0b2ab86af87eb Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 25 Feb 2023 12:50:22 +0800 Subject: [PATCH 039/404] feat(nc-gui): use decimal input for 'pastNumberOfDays', 'nextNumberOfDays' --- .../nc-gui/components/smartsheet/toolbar/FilterInput.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/nc-gui/components/smartsheet/toolbar/FilterInput.vue b/packages/nc-gui/components/smartsheet/toolbar/FilterInput.vue index 5c18353e49..6a2072cba4 100644 --- a/packages/nc-gui/components/smartsheet/toolbar/FilterInput.vue +++ b/packages/nc-gui/components/smartsheet/toolbar/FilterInput.vue @@ -117,9 +117,11 @@ const componentMap: Partial> = $computed(() => { // use MultiSelect for SingleSelect columns for anyof / nanyof filters isSingleSelect: ['anyof', 'nanyof'].includes(props.filter.comparison_op!) ? MultiSelect : SingleSelect, isMultiSelect: MultiSelect, - isDate: ['daysAgo', 'daysFromNow'].includes(props.filter.comparison_sub_op!) ? Decimal : DatePicker, + isDate: ['daysAgo', 'daysFromNow', 'pastNumberOfDays', 'nextNumberOfDays'].includes(props.filter.comparison_sub_op!) + ? Decimal + : DatePicker, isYear: YearPicker, - isDateTime: ['daysAgo', 'daysFromNow'].includes(props.filter.comparison_sub_op!) + isDateTime: ['daysAgo', 'daysFromNow', 'pastNumberOfDays', 'nextNumberOfDays'].includes(props.filter.comparison_sub_op!) ? Decimal : DateTimePicker, isTime: TimePicker, From feec16510e300f895b8713bae62a8b69200d5bfb Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 25 Feb 2023 13:18:35 +0800 Subject: [PATCH 040/404] feat(nocodb): add sub ops for isWithin --- packages/nocodb/src/lib/models/Filter.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/nocodb/src/lib/models/Filter.ts b/packages/nocodb/src/lib/models/Filter.ts index a716040550..2f69aa66f3 100644 --- a/packages/nocodb/src/lib/models/Filter.ts +++ b/packages/nocodb/src/lib/models/Filter.ts @@ -64,7 +64,16 @@ export default class Filter { | 'oneMonthFromNow' | 'daysAgo' | 'daysFromNow' - | 'exactDate'; + | 'exactDate' + // sub ops for isWithin + | 'pastWeek' + | 'pastMonth' + | 'pastYear' + | 'nextWeek' + | 'nextMonth' + | 'nextYear' + | 'pastNumberOfDays' + | 'nextNumberOfDays'; value?: string; From 6a721258283dc102cadaafa548a3bd0d264c39a8 Mon Sep 17 00:00:00 2001 From: Wing-Kam Wong Date: Sat, 25 Feb 2023 13:18:57 +0800 Subject: [PATCH 041/404] feat(nc-gui): include isWithin logic --- .../smartsheet/toolbar/ColumnFilter.vue | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue b/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue index 8d5260d0d5..e33248ed31 100644 --- a/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue +++ b/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue @@ -90,13 +90,23 @@ const filterUpdateCondition = (filter: FilterType, i: number) => { // since `blank`, `empty`, `null` doesn't require value, // hence remove the previous value filter.value = '' - filter.comparison_sub_op = null + filter.comparison_sub_op = '' } else if ([UITypes.Date, UITypes.DateTime].includes(col.uidt as UITypes)) { // for date / datetime, // the input type could be decimal or datepicker / datetime picker // hence remove the previous value filter.value = '' - if (!filter.comparison_sub_op) filter.comparison_sub_op = 'exactDate' + if ( + !comparisonSubOpList(filter.comparison_op!) + .map((op) => op.value) + .includes(filter.comparison_sub_op!) + ) { + if (filter.comparison_op === 'isWithin') { + filter.comparison_sub_op = 'pastNumberOfDays' + } else { + filter.comparison_sub_op = 'exactDate' + } + } } saveOrUpdate(filter, i) filterPrevComparisonOp.value[filter.id] = filter.comparison_op @@ -160,7 +170,11 @@ const selectFilterField = (filter: Filter, index: number) => { )?.[0].value if ([UITypes.Date, UITypes.DateTime].includes(col.uidt as UITypes) && !['blank', 'notblank'].includes(filter.comparison_op)) { - filter.comparison_sub_op = 'exactDate' + if (filter.comparison_op === 'isWithin') { + filter.comparison_sub_op = 'pastNumberOfDays' + } else { + filter.comparison_sub_op = 'exactDate' + } } else { // reset filter.comparison_sub_op = '' @@ -298,13 +312,13 @@ defineExpose({ dropdown-class-name="nc-dropdown-filter-comp-sub-op" @change="filterUpdateCondition(filter, i)" > -