Skip to content

Commit

Permalink
fix: allow async search on smart field (#860)
Browse files Browse the repository at this point in the history
  • Loading branch information
arnaud-moncel authored Oct 20, 2021
1 parent 4e88f24 commit a497af1
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 20 deletions.
2 changes: 1 addition & 1 deletion src/services/query-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ class QueryOptions {
const fieldNames = this._requestedFields.size ? [...this._requestedFields] : null;
const helper = new SearchBuilder(this._model, options, { search, searchExtended }, fieldNames);

const { conditions, include } = helper.performWithSmartFields(this._options.tableAlias);
const { conditions, include } = await helper.performWithSmartFields(this._options.tableAlias);
if (conditions) {
this._where.push(conditions);
} else {
Expand Down
21 changes: 13 additions & 8 deletions src/services/search-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ function SearchBuilder(model, opts, params, fieldNamesRequested) {

this.hasExtendedSearchConditions = () => hasExtendedConditions;

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

schema.fields.filter((field) => field.search).forEach((field) => {
try {
field.search(query, params.search);
} catch (error) {
Interface.logger.error(`Cannot search properly on Smart Field ${field.field}`, error);
}
});
const fieldsWithSearch = schema.fields.filter((field) => field.search);
await Promise.all(
fieldsWithSearch.map(async (field) => {
try {
await field.search(query, params.search);
} catch (error) {
const errorMessage = `Cannot search properly on Smart Field ${field.field}`;
Interface.logger.error(errorMessage, error);
}
return Promise.resolve();
}),
);

return {
include: query.include,
Expand Down
81 changes: 71 additions & 10 deletions test/services/query-options.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,28 @@ import Interface from 'forest-express';
import QueryOptions from '../../src/services/query-options';

describe('services > query-options', () => {
describe('order', () => {
const buildModelMock = (dialect) => {
// Sequelize is created here without connection to a database
const sequelize = new Sequelize({ dialect });

const modelActor = sequelize.define('actor', {});
const modelMovie = sequelize.define('movie', {});
const buildModelMock = (dialect) => {
// Sequelize is created here without connection to a database
const sequelize = new Sequelize({ dialect });

modelActor.belongsTo(modelMovie);
const modelActor = sequelize.define('actor', {});
const modelMovie = sequelize.define('movie', {});

Interface.Schemas = { schemas: { actor: { idField: 'id' } } };
modelActor.belongsTo(modelMovie);

return modelActor;
Interface.Schemas = {
schemas: {
actor: {
idField: 'id',
fields: [{ field: 'smartField', search: (query) => { query.include = 'movie'; } }],
},
},
};

return modelActor;
};

describe('order', () => {
describe('with mssql', () => {
const model = buildModelMock('mssql');

Expand Down Expand Up @@ -56,4 +63,58 @@ describe('services > query-options', () => {
});
});
});

describe('search', () => {
const model = buildModelMock('postgres');

describe('when search on smart field is async', () => {
describe('when promise reject', () => {
it('should display an error message', async () => {
expect.assertions(1);

const loggerErrorSpy = jest.spyOn(Interface.logger, 'error');

const errorThrown = new Error('unexpected error');
Interface.Schemas.schemas.actor.fields[0].search = async () =>
Promise.reject(errorThrown);

const options = new QueryOptions(model);
await options.search('search string', null);
expect(loggerErrorSpy).toHaveBeenCalledWith('Cannot search properly on Smart Field smartField', errorThrown);

loggerErrorSpy.mockClear();
});
});

it('should add the search query', async () => {
expect.assertions(1);

Interface.Schemas.schemas.actor.fields[0].search = async (query) => {
await Promise.resolve();
query.include = ['movie'];
};

const options = new QueryOptions(model);
await options.search('search string', null);
expect(options._customerIncludes).toStrictEqual(['movie']);
});
});

describe('when search on smart field throw an error', () => {
it('should display an error message', async () => {
expect.assertions(1);

const loggerErrorSpy = jest.spyOn(Interface.logger, 'error');

const errorThrown = new Error('unexpected error');
Interface.Schemas.schemas.actor.fields[0].search = () => { throw errorThrown; };

const options = new QueryOptions(model);
await options.search('search string', null);
expect(loggerErrorSpy).toHaveBeenCalledWith('Cannot search properly on Smart Field smartField', errorThrown);

loggerErrorSpy.mockClear();
});
});
});
});
2 changes: 1 addition & 1 deletion types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ export interface SmartFieldSearchQuery {
}

export interface SmartFieldSearcher {
(query: SmartFieldSearchQuery, search: string): SmartFieldSearchQuery;
(query: SmartFieldSearchQuery, search: string): SmartFieldSearchQuery | Promise<SmartFieldSearchQuery>;
}

export interface SmartFieldFiltererFilter {
Expand Down

0 comments on commit a497af1

Please sign in to comment.