Browse Source

Merge pull request #5306 from nocodb/enhancement/filters

enhancement: filters
pull/5322/head
Raju Udava 2 years ago committed by GitHub
parent
commit
b57c85a616
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 20
      packages/nc-gui/utils/filterUtils.ts
  2. 1
      packages/nocodb-sdk/src/lib/UITypes.ts
  3. 8
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts
  4. 10
      packages/nocodb/src/lib/version-upgrader/ncFilterUpgrader_0105003.ts
  5. 21
      tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts
  6. 28
      tests/playwright/setup/xcdb-records.ts
  7. 33
      tests/playwright/tests/filters.spec.ts

20
packages/nc-gui/utils/filterUtils.ts

@ -1,7 +1,7 @@
import { UITypes, isNumericCol, numericUITypes } from 'nocodb-sdk'
import { isNumericCol, numericUITypes, UITypes } from 'nocodb-sdk'
const getEqText = (fieldUiType: UITypes) => {
if (isNumericCol(fieldUiType)) {
if (isNumericCol(fieldUiType) || fieldUiType === UITypes.Time) {
return '='
} else if (
[UITypes.SingleSelect, UITypes.Collaborator, UITypes.LinkToAnotherRecord, UITypes.Date, UITypes.DateTime].includes(
@ -14,7 +14,7 @@ const getEqText = (fieldUiType: UITypes) => {
}
const getNeqText = (fieldUiType: UITypes) => {
if (isNumericCol(fieldUiType)) {
if (isNumericCol(fieldUiType) || fieldUiType === UITypes.Time) {
return '!='
} else if (
[UITypes.SingleSelect, UITypes.Collaborator, UITypes.LinkToAnotherRecord, UITypes.Date, UITypes.DateTime].includes(
@ -112,6 +112,7 @@ export const comparisonOpList = (
UITypes.Collaborator,
UITypes.Date,
UITypes.DateTime,
UITypes.Time,
...numericUITypes,
],
},
@ -126,6 +127,7 @@ export const comparisonOpList = (
UITypes.Collaborator,
UITypes.Date,
UITypes.DateTime,
UITypes.Time,
...numericUITypes,
],
},
@ -143,6 +145,7 @@ export const comparisonOpList = (
UITypes.Lookup,
UITypes.Date,
UITypes.DateTime,
UITypes.Time,
...numericUITypes,
],
},
@ -160,6 +163,7 @@ export const comparisonOpList = (
UITypes.Lookup,
UITypes.Date,
UITypes.DateTime,
UITypes.Time,
...numericUITypes,
],
},
@ -178,6 +182,7 @@ export const comparisonOpList = (
UITypes.Lookup,
UITypes.Date,
UITypes.DateTime,
UITypes.Time,
],
},
{
@ -195,6 +200,7 @@ export const comparisonOpList = (
UITypes.Lookup,
UITypes.Date,
UITypes.DateTime,
UITypes.Time,
],
},
{
@ -225,25 +231,25 @@ export const comparisonOpList = (
text: getGtText(fieldUiType),
value: 'gt',
ignoreVal: false,
includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime],
includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime, UITypes.Time],
},
{
text: getLtText(fieldUiType),
value: 'lt',
ignoreVal: false,
includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime],
includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime, UITypes.Time],
},
{
text: getGteText(fieldUiType),
value: 'gte',
ignoreVal: false,
includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime],
includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime, UITypes.Time],
},
{
text: getLteText(fieldUiType),
value: 'lte',
ignoreVal: false,
includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime],
includedTypes: [...numericUITypes, UITypes.Date, UITypes.DateTime, UITypes.Time],
},
{
text: 'is within',

1
packages/nocodb-sdk/src/lib/UITypes.ts

@ -48,6 +48,7 @@ export const numericUITypes = [
UITypes.Decimal,
UITypes.Rating,
UITypes.Rollup,
UITypes.Year,
];
export function isNumericCol(

8
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts

@ -691,7 +691,9 @@ const parseConditionV2 = async (
qb = qb.whereNull(customWhereClause || field);
if (
!isNumericCol(column.uidt) &&
![UITypes.Date, UITypes.DateTime].includes(column.uidt)
![UITypes.Date, UITypes.DateTime, UITypes.Time].includes(
column.uidt
)
) {
qb = qb.orWhere(field, '');
}
@ -707,7 +709,9 @@ const parseConditionV2 = async (
qb = qb.whereNotNull(customWhereClause || field);
if (
!isNumericCol(column.uidt) &&
![UITypes.Date, UITypes.DateTime].includes(column.uidt)
![UITypes.Date, UITypes.DateTime, UITypes.Time].includes(
column.uidt
)
) {
qb = qb.whereNot(field, '');
}

10
packages/nocodb/src/lib/version-upgrader/ncFilterUpgrader_0105003.ts

@ -5,7 +5,7 @@ import Filter from '../models/Filter';
import type { NcUpgraderCtx } from './NcUpgrader';
import type NcMetaIO from '../meta/NcMetaIO';
// as of 0.105.3, date / datetime filters include `is like` and `is not like` which are not practical
// as of 0.105.3, year, time, date and datetime filters include `is like` and `is not like` which are not practical
// `removeLikeAndNlikeFilters` in this upgrader is simply to remove them
// besides, `null` and `empty` will be migrated to `blank` in `migrateEmptyAndNullFilters`
@ -19,6 +19,9 @@ import type NcMetaIO from '../meta/NcMetaIO';
// - remove `is like` and `is not like`
// - migrate `null` or `empty` filters to `blank`
// - add `exact date` in comparison_sub_op for existing filters `eq` and `neq`
// - Year / Time columns:
// - remove `is like` and `is not like`
// - migrate `null` or `empty` filters to `blank`
function removeLikeAndNlikeFilters(filter: Filter, ncMeta: NcMetaIO) {
const actions = [];
@ -88,6 +91,11 @@ export default async function ({ ncMeta }: NcUpgraderCtx) {
...migrateEmptyAndNullFilters(filter, ncMeta),
...migrateEqAndNeqFilters(filter, ncMeta),
]);
} else if ([UITypes.Time, UITypes.Year].includes(col.uidt)) {
await Promise.all([
...removeLikeAndNlikeFilters(filter, ncMeta),
...migrateEmptyAndNullFilters(filter, ncMeta),
]);
}
}
}

21
tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts

@ -90,6 +90,26 @@ export class ToolbarFilterPage extends BasePage {
if (value) {
let fillFilter: any = null;
switch (dataType) {
case UITypes.Year:
await this.get().locator('.nc-filter-value-select').click();
await this.rootPage.locator(`.ant-picker-dropdown:visible`);
await this.rootPage.locator(`.ant-picker-cell-inner:has-text("${value}")`).click();
break;
case UITypes.Time:
// eslint-disable-next-line no-case-declarations
const time = value.split(':');
await this.get().locator('.nc-filter-value-select').click();
await this.rootPage.locator(`.ant-picker-dropdown:visible`);
await this.rootPage
.locator(`.ant-picker-time-panel-column:nth-child(1)`)
.locator(`.ant-picker-time-panel-cell:has-text("${time[0]}")`)
.click();
await this.rootPage
.locator(`.ant-picker-time-panel-column:nth-child(2)`)
.locator(`.ant-picker-time-panel-cell:has-text("${time[1]}")`)
.click();
await this.rootPage.locator(`.ant-btn-primary:has-text("Ok")`).click();
break;
case UITypes.Date:
if (opSubType === 'exact date') {
await this.get().locator('.nc-filter-value-select').click();
@ -104,7 +124,6 @@ export class ToolbarFilterPage extends BasePage {
});
await this.toolbar.parent.dashboard.waitForLoaderToDisappear();
await this.toolbar.parent.waitLoading();
break;
}
break;
case UITypes.Duration:

28
tests/playwright/setup/xcdb-records.ts

@ -1,6 +1,6 @@
import { ColumnType, UITypes } from 'nocodb-sdk';
const rowMixedValue = (column: ColumnType, index: number) => {
const rowMixedValue = (column: ColumnType, index: number, db?: string) => {
// Array of country names
const countries = [
'Afghanistan',
@ -36,11 +36,33 @@ const rowMixedValue = (column: ColumnType, index: number) => {
null,
];
// compute timezone offset
const offset = new Date().getTimezoneOffset();
const timezoneOffset =
(db === 'mysql' ? (offset < 0 ? '+' : '-') : offset <= 0 ? '+' : '-') +
String(Math.abs(Math.round(offset / 60))).padStart(2, '0') +
':' +
String(Math.abs(offset % 60)).padStart(2, '0');
// Array of random integers, not more than 10000
const numbers = [33, null, 456, 333, 267, 34, 8754, 3234, 44, 33, null];
const decimals = [33.3, 456.34, 333.3, null, 267.5674, 34.0, 8754.0, 3234.547, 44.2647, 33.98, null];
const duration = [60, 120, 180, 3600, 3660, 3720, null, 3780, 60, 120, null];
const time = [
`1999-01-01 02:02:00${timezoneOffset}`,
`1999-01-01 20:20:20${timezoneOffset}`,
`1999-01-01 04:04:00${timezoneOffset}`,
`1999-01-01 02:02:00${timezoneOffset}`,
`1999-01-01 20:20:20${timezoneOffset}`,
`1999-01-01 18:18:18${timezoneOffset}`,
null,
`1999-01-01 02:02:00${timezoneOffset}`,
`1999-01-01 20:20:20${timezoneOffset}`,
`1999-01-01 18:18:18${timezoneOffset}`,
null,
];
const rating = [0, 1, 2, 3, null, 0, 4, 5, 0, 1, null];
const years = [2023, null, 1956, 2023, 1967, 2024, 1954, 1924, 2044, 1923, null];
// Array of random sample email strings (not more than 100 characters)
const emails = [
@ -131,6 +153,10 @@ const rowMixedValue = (column: ColumnType, index: number) => {
return singleSelect[index % singleSelect.length];
case UITypes.MultiSelect:
return multiSelect[index % multiSelect.length];
case UITypes.Year:
return years[index % years.length];
case UITypes.Time:
return time[index % time.length];
default:
return `test-${index}`;
}

33
tests/playwright/tests/filters.spec.ts

@ -13,11 +13,13 @@ let records: Record<string, any>;
const skipList = {
Number: ['is null', 'is not null'],
Year: ['is null', 'is not null'],
Decimal: ['is null', 'is not null'],
Percent: ['is null', 'is not null'],
Currency: ['is null', 'is not null'],
Rating: ['is null', 'is not null', 'is blank', 'is not blank'],
Duration: ['is null', 'is not null'],
Time: ['is null', 'is not null'],
SingleLineText: [],
MultiLineText: [],
Email: [],
@ -126,6 +128,13 @@ test.describe('Filter Tests: Numerical', () => {
isLikeStringDerived = parseInt(isLikeString.split(':')[0]) * 3600 + parseInt(isLikeString.split(':')[1]) * 60;
}
// convert r[Time] in format 2021-01-01 00:00:00+05.30 to 00:00:00
if (dataType === 'Time') {
records.list.forEach(r => {
if (r[dataType]?.length > 8) r[dataType] = r[dataType]?.split(' ')[1]?.split(/[+-]/)[0];
});
}
const filterList = [
{
op: '=',
@ -150,12 +159,12 @@ test.describe('Filter Tests: Numerical', () => {
{
op: 'is blank',
value: '',
rowCount: records.list.filter(r => r[dataType] === null).length,
rowCount: records.list.filter(r => r[dataType] === null || r[dataType] === undefined).length,
},
{
op: 'is not blank',
value: '',
rowCount: records.list.filter(r => r[dataType] !== null).length,
rowCount: records.list.filter(r => r[dataType] !== null && r[dataType] !== undefined).length,
},
{
op: '>',
@ -255,6 +264,16 @@ test.describe('Filter Tests: Numerical', () => {
title: 'Rating',
uidt: UITypes.Rating,
},
{
column_name: 'Year',
title: 'Year',
uidt: UITypes.Year,
},
{
column_name: 'Time',
title: 'Time',
uidt: UITypes.Time,
},
];
try {
@ -274,6 +293,8 @@ test.describe('Filter Tests: Numerical', () => {
Percent: rowMixedValue(columns[4], i),
Duration: rowMixedValue(columns[5], i),
Rating: rowMixedValue(columns[6], i),
Year: rowMixedValue(columns[7], i),
Time: rowMixedValue(columns[8], i, context.dbType),
};
rowAttributes.push(row);
}
@ -308,6 +329,14 @@ test.describe('Filter Tests: Numerical', () => {
test('Filter: Duration', async () => {
await numBasedFilterTest('Duration', '00:01', '01:03');
});
test('Filter: Year', async () => {
await numBasedFilterTest('Year', '2023', '2024');
});
test('Filter: Time', async () => {
await numBasedFilterTest('Time', '02:02:00', '04:04:00');
});
});
// Text based filters

Loading…
Cancel
Save