Skip to content

Commit a497af1

Browse files
fix: allow async search on smart field (#860)
1 parent 4e88f24 commit a497af1

File tree

4 files changed

+86
-20
lines changed

4 files changed

+86
-20
lines changed

src/services/query-options.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ class QueryOptions {
181181
const fieldNames = this._requestedFields.size ? [...this._requestedFields] : null;
182182
const helper = new SearchBuilder(this._model, options, { search, searchExtended }, fieldNames);
183183

184-
const { conditions, include } = helper.performWithSmartFields(this._options.tableAlias);
184+
const { conditions, include } = await helper.performWithSmartFields(this._options.tableAlias);
185185
if (conditions) {
186186
this._where.push(conditions);
187187
} else {

src/services/search-builder.js

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ function SearchBuilder(model, opts, params, fieldNamesRequested) {
7070

7171
this.hasExtendedSearchConditions = () => hasExtendedConditions;
7272

73-
this.performWithSmartFields = (associationName) => {
73+
this.performWithSmartFields = async (associationName) => {
7474
// Retrocompatibility: customers which implement search on smart fields are expected to
7575
// inject their conditions at .where[Op.and][0][Op.or].push(searchCondition)
7676
// https://docs.forestadmin.com/documentation/reference-guide/fields/create-and-manage-smart-fields
@@ -79,13 +79,18 @@ function SearchBuilder(model, opts, params, fieldNamesRequested) {
7979
where: { [OPERATORS.AND]: [this.perform(associationName)] },
8080
};
8181

82-
schema.fields.filter((field) => field.search).forEach((field) => {
83-
try {
84-
field.search(query, params.search);
85-
} catch (error) {
86-
Interface.logger.error(`Cannot search properly on Smart Field ${field.field}`, error);
87-
}
88-
});
82+
const fieldsWithSearch = schema.fields.filter((field) => field.search);
83+
await Promise.all(
84+
fieldsWithSearch.map(async (field) => {
85+
try {
86+
await field.search(query, params.search);
87+
} catch (error) {
88+
const errorMessage = `Cannot search properly on Smart Field ${field.field}`;
89+
Interface.logger.error(errorMessage, error);
90+
}
91+
return Promise.resolve();
92+
}),
93+
);
8994

9095
return {
9196
include: query.include,

test/services/query-options.test.js

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,28 @@ import Interface from 'forest-express';
33
import QueryOptions from '../../src/services/query-options';
44

55
describe('services > query-options', () => {
6-
describe('order', () => {
7-
const buildModelMock = (dialect) => {
8-
// Sequelize is created here without connection to a database
9-
const sequelize = new Sequelize({ dialect });
10-
11-
const modelActor = sequelize.define('actor', {});
12-
const modelMovie = sequelize.define('movie', {});
6+
const buildModelMock = (dialect) => {
7+
// Sequelize is created here without connection to a database
8+
const sequelize = new Sequelize({ dialect });
139

14-
modelActor.belongsTo(modelMovie);
10+
const modelActor = sequelize.define('actor', {});
11+
const modelMovie = sequelize.define('movie', {});
1512

16-
Interface.Schemas = { schemas: { actor: { idField: 'id' } } };
13+
modelActor.belongsTo(modelMovie);
1714

18-
return modelActor;
15+
Interface.Schemas = {
16+
schemas: {
17+
actor: {
18+
idField: 'id',
19+
fields: [{ field: 'smartField', search: (query) => { query.include = 'movie'; } }],
20+
},
21+
},
1922
};
2023

24+
return modelActor;
25+
};
26+
27+
describe('order', () => {
2128
describe('with mssql', () => {
2229
const model = buildModelMock('mssql');
2330

@@ -56,4 +63,58 @@ describe('services > query-options', () => {
5663
});
5764
});
5865
});
66+
67+
describe('search', () => {
68+
const model = buildModelMock('postgres');
69+
70+
describe('when search on smart field is async', () => {
71+
describe('when promise reject', () => {
72+
it('should display an error message', async () => {
73+
expect.assertions(1);
74+
75+
const loggerErrorSpy = jest.spyOn(Interface.logger, 'error');
76+
77+
const errorThrown = new Error('unexpected error');
78+
Interface.Schemas.schemas.actor.fields[0].search = async () =>
79+
Promise.reject(errorThrown);
80+
81+
const options = new QueryOptions(model);
82+
await options.search('search string', null);
83+
expect(loggerErrorSpy).toHaveBeenCalledWith('Cannot search properly on Smart Field smartField', errorThrown);
84+
85+
loggerErrorSpy.mockClear();
86+
});
87+
});
88+
89+
it('should add the search query', async () => {
90+
expect.assertions(1);
91+
92+
Interface.Schemas.schemas.actor.fields[0].search = async (query) => {
93+
await Promise.resolve();
94+
query.include = ['movie'];
95+
};
96+
97+
const options = new QueryOptions(model);
98+
await options.search('search string', null);
99+
expect(options._customerIncludes).toStrictEqual(['movie']);
100+
});
101+
});
102+
103+
describe('when search on smart field throw an error', () => {
104+
it('should display an error message', async () => {
105+
expect.assertions(1);
106+
107+
const loggerErrorSpy = jest.spyOn(Interface.logger, 'error');
108+
109+
const errorThrown = new Error('unexpected error');
110+
Interface.Schemas.schemas.actor.fields[0].search = () => { throw errorThrown; };
111+
112+
const options = new QueryOptions(model);
113+
await options.search('search string', null);
114+
expect(loggerErrorSpy).toHaveBeenCalledWith('Cannot search properly on Smart Field smartField', errorThrown);
115+
116+
loggerErrorSpy.mockClear();
117+
});
118+
});
119+
});
59120
});

types/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ export interface SmartFieldSearchQuery {
227227
}
228228

229229
export interface SmartFieldSearcher {
230-
(query: SmartFieldSearchQuery, search: string): SmartFieldSearchQuery;
230+
(query: SmartFieldSearchQuery, search: string): SmartFieldSearchQuery | Promise<SmartFieldSearchQuery>;
231231
}
232232

233233
export interface SmartFieldFiltererFilter {

0 commit comments

Comments
 (0)