Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 32 additions & 10 deletions docs/agent-customization/search.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@ Two search modes are supported: normal and extended.

![Extended search call to action](../assets/search-bar-extended.png)

When not defined otherwise by the [datasource](../datasources/README.md), the search behavior is to attempt to search within columns of the collection (in normal mode), or columns of the collection of direct relations (in extended mode).

## Default behavior

By default, Forest Admin won't search only on some columns, depending on their respective types.
When not defined otherwise by the [datasource](../datasources/README.md), the search behavior is to attempt to search within columns of the collection (in normal mode), or columns of the collection of direct relations (in extended mode).

| Column Type | Default search behavior |
| ----------- | ---------------------------------------------------------------------------------------- |
| Enum | Column is equal to the search string (if the search string contains a value of the enum) |
| Number | Column is equal to the search string (if the search string is numeric) |
| String | Column contains the search string (case-insensitive) |
| Uuid | Column is equal to the search string (if the search string contains an uuid) |
| Other types | Column is ignored by the default search handler |
By default, Forest Admin will search only on some columns, depending on their respective types.

| Column Type | Default search behavior |
| ----------- | ---------------------------------------------------------------------- |
| Enum | Column is equal to the search string (case-insensitive) |
| Number | Column is equal to the search string (if the search string is numeric) |
| String | Column contains the search string (case-insensitive) |
| Uuid | Column is equal to the search string |
| Other types | Column is ignored by the default search handler |

## Customization

Expand All @@ -34,6 +34,28 @@ For instance:
- Search only on the columns which are relevant to your use-case.
- Use a full-text indexes of your data (i.e Postgres `tsquery` and `tsvector`, Algolia, Elastic search, ...)

In order to customize the search bar, you must define a handler which returns a [`ConditionTree`](../under-the-hood/queries/filters.md#condition-trees).

### Making the search case-sensitive by default

In this example, we use the `searchExtended` condition to toggle between case-sensitive and insensitive search.

```javascript
agent.customizeCollection('people', collection =>
collection.replaceSearch((searchString, extendedMode) => {
const operator = extendedMode ? 'Contains' : 'IContains';

return {
aggregator: 'Or',
conditions: [
{ field: 'firstName', operator, value: searchString },
{ field: 'lastName', operator, value: searchString },
],
};
}),
);
```

### Changing searched columns

```javascript
Expand Down
37 changes: 26 additions & 11 deletions packages/datasource-sequelize/src/utils/query-converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Sort,
} from '@forestadmin/datasource-toolkit';
import {
Dialect,
IncludeOptions,
ModelDefined,
Op,
Expand All @@ -23,9 +24,11 @@ import { Where } from 'sequelize/types/utils';

export default class QueryConverter {
private model: ModelDefined<unknown, unknown>;
private dialect: Dialect;

constructor(model: ModelDefined<unknown, unknown>) {
this.model = model;
this.dialect = this.model.sequelize.getDialect() as Dialect;
}

private asArray(value: unknown) {
Expand All @@ -35,8 +38,8 @@ export default class QueryConverter {
}

private makeWhereClause(
operator: Operator,
field: string,
operator: Operator,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
value?: any,
): WhereOperators | OrOperator | Where {
Expand All @@ -45,12 +48,28 @@ export default class QueryConverter {
switch (operator) {
case 'Blank':
return {
[Op.or]: [this.makeWhereClause('Missing', field) as OrOperator, { [Op.eq]: '' }],
[Op.or]: [this.makeWhereClause(field, 'Missing') as OrOperator, { [Op.eq]: '' }],
};

case 'Like':
if (this.dialect === 'sqlite')
return where(col(field), 'GLOB', value.replace(/%/g, '*').replace(/_/g, '?'));
if (this.dialect === 'mysql' || this.dialect === 'mariadb')
return where(fn('BINARY', col(field)), 'LIKE', value);

return { [Op.like]: value };

case 'ILike':
if (this.dialect === 'postgres') return { [Op.iLike]: value };
if (this.dialect === 'mysql' || this.dialect === 'mariadb' || this.dialect === 'sqlite')
return { [Op.like]: value };

return where(fn('LOWER', col(field)), 'LIKE', value.toLocaleLowerCase());

case 'NotContains':
return {
[Op.not]: this.makeWhereClause(field, 'Like', `%${value}%`) as WhereOperators,
};
case 'Contains':
return where(fn('LOWER', col(field)), 'LIKE', `%${value.toLocaleLowerCase()}%`);
case 'EndsWith':
return where(fn('LOWER', col(field)), 'LIKE', `%${value.toLocaleLowerCase()}`);
case 'Equal':
return { [Op.eq]: value };
case 'GreaterThan':
Expand All @@ -63,16 +82,12 @@ export default class QueryConverter {
return { [Op.lt]: value };
case 'Missing':
return { [Op.is]: null };
case 'NotContains':
return where(fn('LOWER', col(field)), 'NOT LIKE', `%${value.toLocaleLowerCase()}%`);
case 'NotEqual':
return { [Op.ne]: value };
case 'NotIn':
return { [Op.notIn]: this.asArray(value) };
case 'Present':
return { [Op.ne]: null };
case 'StartsWith':
return where(fn('LOWER', col(field)), 'LIKE', `${value.toLocaleLowerCase()}%`);
default:
throw new Error(`Unsupported operator: "${operator}".`);
}
Expand Down Expand Up @@ -144,8 +159,8 @@ export default class QueryConverter {
}

sequelizeWhereClause[isRelation ? `$${safeField}$` : safeField] = this.makeWhereClause(
operator,
safeField,
operator,
value,
);
} else {
Expand Down
4 changes: 1 addition & 3 deletions packages/datasource-sequelize/src/utils/type-converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,13 @@ export default class TypeConverter {
case 'String':
return new Set<Operator>([
...TypeConverter.baseOperators,
'Contains',
'EndsWith',
'In',
'Like',
'ILike',
'LongerThan',
'NotContains',
'NotIn',
'ShorterThan',
'StartsWith',
]);
case 'Date':
case 'Dateonly':
Expand Down
Loading