Skip to content

Commit bfeee28

Browse files
authored
Merge pull request #443 from devforth/feature/AdminForth/1131/add-filters.isempty-and-filter
feat: add IS_EMPTY and IS_NOT_EMPTY filters
2 parents cfe050b + 3a609c4 commit bfeee28

File tree

8 files changed

+71
-10
lines changed

8 files changed

+71
-10
lines changed

adminforth/dataConnectors/baseConnector.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,9 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
103103
}
104104
// Either compare with value or with rightField (field-to-field). If rightField is set, value must be undefined.
105105
const comparingWithRightField = filtersAsSingle.rightField !== undefined && filtersAsSingle.rightField !== null;
106-
if (!comparingWithRightField && filtersAsSingle.value === undefined) {
106+
const isEmptyOperator = filters.operator === AdminForthFilterOperators.IS_EMPTY || filters.operator === AdminForthFilterOperators.IS_NOT_EMPTY;
107+
108+
if (!comparingWithRightField && !isEmptyOperator && filtersAsSingle.value === undefined) {
107109
return { ok: false, error: `Field "value" not specified in filter object: ${JSON.stringify(filters)}` };
108110
}
109111
if (comparingWithRightField && filtersAsSingle.value !== undefined) {
@@ -118,7 +120,7 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
118120
if (![AdminForthFilterOperators.EQ, AdminForthFilterOperators.NE, AdminForthFilterOperators.GT,
119121
AdminForthFilterOperators.LT, AdminForthFilterOperators.GTE, AdminForthFilterOperators.LTE,
120122
AdminForthFilterOperators.LIKE, AdminForthFilterOperators.ILIKE, AdminForthFilterOperators.IN,
121-
AdminForthFilterOperators.NIN].includes(filters.operator)) {
123+
AdminForthFilterOperators.NIN, AdminForthFilterOperators.IS_EMPTY, AdminForthFilterOperators.IS_NOT_EMPTY].includes(filters.operator)) {
122124
return { ok: false, error: `Field "operator" has wrong value in filter object: ${JSON.stringify(filters)}` };
123125
}
124126
const fieldObj = resource.dataSourceColumns.find((col) => col.name == filtersAsSingle.field);
@@ -151,6 +153,12 @@ export default class AdminForthBaseConnector implements IAdminForthDataSourceCon
151153
throw new Error(`Field '${filtersAsSingle.rightField}' not found in resource '${resource.resourceId}'. ${similar ? `Did you mean '${similar}'?` : ''}`);
152154
}
153155
// No value conversion needed for field-to-field comparison here
156+
} else if (isEmptyOperator) {
157+
// IS_EMPTY and IS_NOT_EMPTY don't need value normalization
158+
// Set value to null if not already set
159+
if (filtersAsSingle.value === undefined) {
160+
filtersAsSingle.value = null;
161+
}
154162
} else if (filters.operator == AdminForthFilterOperators.IN || filters.operator == AdminForthFilterOperators.NIN) {
155163
if (!Array.isArray(filters.value)) {
156164
return { ok: false, error: `Value for operator '${filters.operator}' should be an array, in filter object: ${JSON.stringify(filters) }` };

adminforth/dataConnectors/clickhouse.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,8 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
217217
[AdminForthFilterOperators.NIN]: 'NOT IN',
218218
[AdminForthFilterOperators.AND]: 'AND',
219219
[AdminForthFilterOperators.OR]: 'OR',
220+
[AdminForthFilterOperators.IS_EMPTY]: 'IS NULL',
221+
[AdminForthFilterOperators.IS_NOT_EMPTY]: 'IS NOT NULL',
220222
};
221223

222224
SortDirectionsMap = {
@@ -239,6 +241,11 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
239241
let placeholder = `{f$?:${column._underlineType}}`;
240242
let operator = this.OperatorsMap[filter.operator];
241243

244+
// Handle IS_EMPTY and IS_NOT_EMPTY operators
245+
if (filter.operator == AdminForthFilterOperators.IS_EMPTY || filter.operator == AdminForthFilterOperators.IS_NOT_EMPTY) {
246+
return `${field} ${operator}`;
247+
}
248+
242249
if (column._underlineType.startsWith('Decimal')) {
243250
field = `toDecimal64(${field}, 8)`;
244251
placeholder = `toDecimal64({f$?:String}, 8)`;
@@ -292,7 +299,11 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
292299
return [];
293300
}
294301
// filter is a Single filter
295-
if (filter.operator == AdminForthFilterOperators.LIKE || filter.operator == AdminForthFilterOperators.ILIKE) {
302+
303+
// Handle IS_EMPTY and IS_NOT_EMPTY operators - no params needed
304+
if (filter.operator == AdminForthFilterOperators.IS_EMPTY || filter.operator == AdminForthFilterOperators.IS_NOT_EMPTY) {
305+
return [];
306+
} else if (filter.operator == AdminForthFilterOperators.LIKE || filter.operator == AdminForthFilterOperators.ILIKE) {
296307
return [{ 'f': `%${filter.value}%` }];
297308
} else if (filter.operator == AdminForthFilterOperators.IN || filter.operator == AdminForthFilterOperators.NIN) {
298309
return [{ 'p': filter.value }];

adminforth/dataConnectors/mongo.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ class MongoConnector extends AdminForthBaseConnector implements IAdminForthDataS
5858
[AdminForthFilterOperators.NIN]: (value) => ({ $nin: value }),
5959
[AdminForthFilterOperators.AND]: (value) => ({ $and: value }),
6060
[AdminForthFilterOperators.OR]: (value) => ({ $or: value }),
61+
[AdminForthFilterOperators.IS_EMPTY]: () => null,
62+
[AdminForthFilterOperators.IS_NOT_EMPTY]: () => ({ $ne: null }),
6163
};
6264

6365
SortDirectionsMap = {

adminforth/dataConnectors/mysql.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class MysqlConnector extends AdminForthBaseConnector implements IAdminForthDataS
3232
[AdminForthFilterOperators.NIN]: 'NOT IN',
3333
[AdminForthFilterOperators.AND]: 'AND',
3434
[AdminForthFilterOperators.OR]: 'OR',
35+
[AdminForthFilterOperators.IS_EMPTY]: 'IS NULL',
36+
[AdminForthFilterOperators.IS_NOT_EMPTY]: 'IS NOT NULL',
3537
};
3638

3739
SortDirectionsMap = {
@@ -215,7 +217,11 @@ class MysqlConnector extends AdminForthBaseConnector implements IAdminForthDataS
215217
let placeholder = '?';
216218
let field = (filter as IAdminForthSingleFilter).field;
217219
let operator = this.OperatorsMap[filter.operator];
218-
if (filter.operator == AdminForthFilterOperators.IN || filter.operator == AdminForthFilterOperators.NIN) {
220+
221+
// Handle IS_EMPTY and IS_NOT_EMPTY operators
222+
if (filter.operator == AdminForthFilterOperators.IS_EMPTY || filter.operator == AdminForthFilterOperators.IS_NOT_EMPTY) {
223+
return `${field} ${operator}`;
224+
} else if (filter.operator == AdminForthFilterOperators.IN || filter.operator == AdminForthFilterOperators.NIN) {
219225
placeholder = `(${(filter as IAdminForthSingleFilter).value.map(() => '?').join(', ')})`;
220226
} else if (filter.operator == AdminForthFilterOperators.ILIKE) {
221227
placeholder = `LOWER(?)`;
@@ -261,7 +267,11 @@ class MysqlConnector extends AdminForthBaseConnector implements IAdminForthDataS
261267
return [];
262268
}
263269
// filter is a Single filter
264-
if (filter.operator == AdminForthFilterOperators.LIKE || filter.operator == AdminForthFilterOperators.ILIKE) {
270+
271+
// Handle IS_EMPTY and IS_NOT_EMPTY operators - no params needed
272+
if (filter.operator == AdminForthFilterOperators.IS_EMPTY || filter.operator == AdminForthFilterOperators.IS_NOT_EMPTY) {
273+
return [];
274+
} else if (filter.operator == AdminForthFilterOperators.LIKE || filter.operator == AdminForthFilterOperators.ILIKE) {
265275
return [`%${filter.value}%`];
266276
} else if (filter.operator == AdminForthFilterOperators.IN || filter.operator == AdminForthFilterOperators.NIN) {
267277
return filter.value;

adminforth/dataConnectors/postgres.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ class PostgresConnector extends AdminForthBaseConnector implements IAdminForthDa
3838
[AdminForthFilterOperators.NIN]: 'NOT IN',
3939
[AdminForthFilterOperators.AND]: 'AND',
4040
[AdminForthFilterOperators.OR]: 'OR',
41+
[AdminForthFilterOperators.IS_EMPTY]: 'IS NULL',
42+
[AdminForthFilterOperators.IS_NOT_EMPTY]: 'IS NOT NULL',
4143
};
4244

4345
SortDirectionsMap = {
@@ -252,7 +254,11 @@ class PostgresConnector extends AdminForthBaseConnector implements IAdminForthDa
252254
let field = (filter as IAdminForthSingleFilter).field;
253255
const fieldData = resource.dataSourceColumns.find((col) => col.name == field);
254256
let operator = this.OperatorsMap[filter.operator];
255-
if (filter.operator == AdminForthFilterOperators.IN || filter.operator == AdminForthFilterOperators.NIN) {
257+
258+
// Handle IS_EMPTY and IS_NOT_EMPTY operators
259+
if (filter.operator == AdminForthFilterOperators.IS_EMPTY || filter.operator == AdminForthFilterOperators.IS_NOT_EMPTY) {
260+
return `"${field}" ${operator}`;
261+
} else if (filter.operator == AdminForthFilterOperators.IN || filter.operator == AdminForthFilterOperators.NIN) {
256262
placeholder = `(${filter.value.map(() => placeholder).join(', ')})`;
257263
}
258264

@@ -293,7 +299,11 @@ class PostgresConnector extends AdminForthBaseConnector implements IAdminForthDa
293299
return [];
294300
}
295301
// filter is a Single filter
296-
if (filter.operator == AdminForthFilterOperators.LIKE || filter.operator == AdminForthFilterOperators.ILIKE) {
302+
303+
// Handle IS_EMPTY and IS_NOT_EMPTY operators - no params needed
304+
if (filter.operator == AdminForthFilterOperators.IS_EMPTY || filter.operator == AdminForthFilterOperators.IS_NOT_EMPTY) {
305+
return [];
306+
} else if (filter.operator == AdminForthFilterOperators.LIKE || filter.operator == AdminForthFilterOperators.ILIKE) {
297307
return [`%${filter.value}%`];
298308
} else if (filter.operator == AdminForthFilterOperators.IN || filter.operator == AdminForthFilterOperators.NIN) {
299309
return filter.value;

adminforth/dataConnectors/sqlite.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ class SQLiteConnector extends AdminForthBaseConnector implements IAdminForthData
176176
[AdminForthFilterOperators.NIN]: 'NOT IN',
177177
[AdminForthFilterOperators.AND]: 'AND',
178178
[AdminForthFilterOperators.OR]: 'OR',
179+
[AdminForthFilterOperators.IS_EMPTY]: 'IS NULL',
180+
[AdminForthFilterOperators.IS_NOT_EMPTY]: 'IS NOT NULL',
179181
};
180182

181183
SortDirectionsMap = {
@@ -196,7 +198,11 @@ class SQLiteConnector extends AdminForthBaseConnector implements IAdminForthData
196198
let placeholder = '?';
197199
let field = (filter as IAdminForthSingleFilter).field;
198200
let operator = this.OperatorsMap[filter.operator];
199-
if (filter.operator == AdminForthFilterOperators.IN || filter.operator == AdminForthFilterOperators.NIN) {
201+
202+
// Handle IS_EMPTY and IS_NOT_EMPTY operators
203+
if (filter.operator == AdminForthFilterOperators.IS_EMPTY || filter.operator == AdminForthFilterOperators.IS_NOT_EMPTY) {
204+
return `${field} ${operator}`;
205+
} else if (filter.operator == AdminForthFilterOperators.IN || filter.operator == AdminForthFilterOperators.NIN) {
200206
placeholder = `(${filter.value.map(() => '?').join(', ')})`;
201207
} else if (filter.operator == AdminForthFilterOperators.ILIKE) {
202208
placeholder = `LOWER(?)`;
@@ -240,7 +246,11 @@ class SQLiteConnector extends AdminForthBaseConnector implements IAdminForthData
240246
return [];
241247
}
242248
// filter is a Single filter
243-
if (filter.operator == AdminForthFilterOperators.LIKE || filter.operator == AdminForthFilterOperators.ILIKE) {
249+
250+
// Handle IS_EMPTY and IS_NOT_EMPTY operators - no params needed
251+
if (filter.operator == AdminForthFilterOperators.IS_EMPTY || filter.operator == AdminForthFilterOperators.IS_NOT_EMPTY) {
252+
return [];
253+
} else if (filter.operator == AdminForthFilterOperators.LIKE || filter.operator == AdminForthFilterOperators.ILIKE) {
244254
return [`%${filter.value}%`];
245255
} else if (filter.operator == AdminForthFilterOperators.IN || filter.operator == AdminForthFilterOperators.NIN) {
246256
return filter.value;

adminforth/types/Back.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ export interface IAdminForthSingleFilter {
129129
operator?: AdminForthFilterOperators.EQ | AdminForthFilterOperators.NE
130130
| AdminForthFilterOperators.GT | AdminForthFilterOperators.LT | AdminForthFilterOperators.GTE
131131
| AdminForthFilterOperators.LTE | AdminForthFilterOperators.LIKE | AdminForthFilterOperators.ILIKE
132-
| AdminForthFilterOperators.IN | AdminForthFilterOperators.NIN;
132+
| AdminForthFilterOperators.IN | AdminForthFilterOperators.NIN | AdminForthFilterOperators.IS_EMPTY | AdminForthFilterOperators.IS_NOT_EMPTY;
133133
value?: any;
134134
rightField?: string;
135135
insecureRawSQL?: string;
@@ -1284,6 +1284,14 @@ export class Filters {
12841284
subFilters,
12851285
};
12861286
}
1287+
1288+
static IS_EMPTY(field: string): IAdminForthSingleFilter {
1289+
return { field, operator: AdminForthFilterOperators.IS_EMPTY, value: null };
1290+
}
1291+
1292+
static IS_NOT_EMPTY(field: string): IAdminForthSingleFilter {
1293+
return { field, operator: AdminForthFilterOperators.IS_NOT_EMPTY, value: null };
1294+
}
12871295
}
12881296

12891297
export type FDataSort = (field: string, direction: AdminForthSortDirections) => IAdminForthSort;

adminforth/types/Common.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export enum AdminForthFilterOperators {
2828
NIN = 'nin',
2929
AND = 'and',
3030
OR = 'or',
31+
IS_EMPTY = 'isEmpty',
32+
IS_NOT_EMPTY = 'isNotEmpty',
3133
};
3234

3335
export type FilterParams = {

0 commit comments

Comments
 (0)