mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 13:50:31 +00:00
feat: optimize dynamic list service.
feat: inactive mode for accounts, items, customers and vendors services.
This commit is contained in:
@@ -1,52 +1,73 @@
|
||||
import { forEach, uniqBy } from 'lodash';
|
||||
import { buildFilterRolesJoins } from 'lib/ViewRolesBuilder';
|
||||
import { IModel } from 'interfaces';
|
||||
import DynamicFilterAbstructor from './DynamicFilterAbstructor';
|
||||
import { IDynamicFilter, IFilterRole, IModel } from 'interfaces';
|
||||
|
||||
export default class DynamicFilter {
|
||||
model: IModel;
|
||||
tableName: string;
|
||||
export default class DynamicFilter extends DynamicFilterAbstructor{
|
||||
private model: IModel;
|
||||
private tableName: string;
|
||||
private dynamicFilters: IDynamicFilter[];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param {String} tableName -
|
||||
*/
|
||||
constructor(model) {
|
||||
super();
|
||||
|
||||
this.model = model;
|
||||
this.tableName = model.tableName;
|
||||
this.filters = [];
|
||||
this.dynamicFilters = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set filter.
|
||||
* @param {*} filterRole - Filter role.
|
||||
* Registers the given dynamic filter.
|
||||
* @param {IDynamicFilter} filterRole - Filter role.
|
||||
*/
|
||||
setFilter(filterRole) {
|
||||
filterRole.setModel(this.model);
|
||||
this.filters.push(filterRole);
|
||||
public setFilter = (dynamicFilter: IDynamicFilter) => {
|
||||
dynamicFilter.setModel(this.model);
|
||||
this.dynamicFilters.push(dynamicFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve dynamic filter build queries.
|
||||
* @returns
|
||||
*/
|
||||
private dynamicFiltersBuildQuery = () => {
|
||||
return this.dynamicFilters.map((filter) => {
|
||||
return filter.buildQuery()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve dynamic filter roles.
|
||||
* @returns {IFilterRole[]}
|
||||
*/
|
||||
private dynamicFilterTableColumns = (): IFilterRole[] => {
|
||||
const localFilterRoles = [];
|
||||
|
||||
this.dynamicFilters.forEach((dynamicFilter) => {
|
||||
const { filterRoles } = dynamicFilter;
|
||||
|
||||
localFilterRoles.push(
|
||||
...(Array.isArray(filterRoles) ? filterRoles : [filterRoles])
|
||||
);
|
||||
});
|
||||
return localFilterRoles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds queries of filter roles.
|
||||
*/
|
||||
buildQuery() {
|
||||
const buildersCallbacks = [];
|
||||
const tableColumns = [];
|
||||
|
||||
this.filters.forEach((filter) => {
|
||||
const { filterRoles } = filter;
|
||||
|
||||
buildersCallbacks.push(filter.buildQuery());
|
||||
tableColumns.push(
|
||||
...(Array.isArray(filterRoles) ? filterRoles : [filterRoles])
|
||||
);
|
||||
});
|
||||
public buildQuery = () => {
|
||||
const buildersCallbacks = this.dynamicFiltersBuildQuery();
|
||||
const tableColumns = this.dynamicFilterTableColumns();
|
||||
|
||||
return (builder) => {
|
||||
buildersCallbacks.forEach((builderCallback) => {
|
||||
builderCallback(builder);
|
||||
});
|
||||
|
||||
buildFilterRolesJoins(
|
||||
this.buildFilterRolesJoins(
|
||||
this.model,
|
||||
uniqBy(tableColumns, 'columnKey')
|
||||
)(builder);
|
||||
@@ -56,10 +77,10 @@ export default class DynamicFilter {
|
||||
/**
|
||||
* Retrieve response metadata from all filters adapters.
|
||||
*/
|
||||
getResponseMeta() {
|
||||
public getResponseMeta = () => {
|
||||
const responseMeta = {};
|
||||
|
||||
this.filters.forEach((filter) => {
|
||||
this.dynamicFilters.forEach((filter) => {
|
||||
const { responseMeta: filterMeta } = filter;
|
||||
|
||||
forEach(filterMeta, (value, key) => {
|
||||
|
||||
37
server/src/lib/DynamicFilter/DynamicFilterAbstructor.ts
Normal file
37
server/src/lib/DynamicFilter/DynamicFilterAbstructor.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { IModel, IFilterRole } from 'interfaces';
|
||||
|
||||
export default class DynamicFilterAbstructor {
|
||||
/**
|
||||
* Extract relation table name from relation.
|
||||
* @param {String} column -
|
||||
* @return {String} - join relation table.
|
||||
*/
|
||||
protected getTableFromRelationColumn = (column: string) => {
|
||||
const splitedColumn = column.split('.');
|
||||
return splitedColumn.length > 0 ? splitedColumn[0] : '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds view roles join queries.
|
||||
* @param {String} tableName - Table name.
|
||||
* @param {Array} roles - Roles.
|
||||
*/
|
||||
protected buildFilterRolesJoins = (model: IModel, roles: IFilterRole[]) => {
|
||||
return (builder) => {
|
||||
roles.forEach((role) => {
|
||||
const field = model.getField(role.fieldKey);
|
||||
|
||||
if (field.relation) {
|
||||
const joinTable = this.getTableFromRelationColumn(field.relation);
|
||||
|
||||
builder.join(
|
||||
joinTable,
|
||||
`${model.tableName}.${field.column}`,
|
||||
'=',
|
||||
field.relation
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
import { difference } from 'lodash';
|
||||
import DynamicFilterRoleAbstructor from 'lib/DynamicFilter/DynamicFilterRoleAbstructor';
|
||||
import { buildFilterQuery } from 'lib/ViewRolesBuilder';
|
||||
import DynamicFilterRoleAbstructor from './DynamicFilterRoleAbstructor';
|
||||
import { IFilterRole } from 'interfaces';
|
||||
|
||||
export default class FilterRoles extends DynamicFilterRoleAbstructor {
|
||||
filterRoles: IFilterRole[];
|
||||
private filterRoles: IFilterRole[];
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
@@ -13,7 +11,7 @@ export default class FilterRoles extends DynamicFilterRoleAbstructor {
|
||||
*/
|
||||
constructor(filterRoles: IFilterRole[]) {
|
||||
super();
|
||||
|
||||
|
||||
this.filterRoles = filterRoles;
|
||||
this.setResponseMeta();
|
||||
}
|
||||
@@ -24,7 +22,7 @@ export default class FilterRoles extends DynamicFilterRoleAbstructor {
|
||||
*/
|
||||
private buildLogicExpression(): string {
|
||||
let expression = '';
|
||||
|
||||
|
||||
this.filterRoles.forEach((role, index) => {
|
||||
expression +=
|
||||
index === 0 ? `${role.index} ` : `${role.condition} ${role.index} `;
|
||||
@@ -35,17 +33,22 @@ export default class FilterRoles extends DynamicFilterRoleAbstructor {
|
||||
/**
|
||||
* Builds database query of view roles.
|
||||
*/
|
||||
buildQuery() {
|
||||
protected buildQuery() {
|
||||
const logicExpression = this.buildLogicExpression();
|
||||
|
||||
return (builder) => {
|
||||
const logicExpression = this.buildLogicExpression();
|
||||
buildFilterQuery(this.model, this.filterRoles, logicExpression)(builder);
|
||||
this.buildFilterQuery(
|
||||
this.model,
|
||||
this.filterRoles,
|
||||
logicExpression
|
||||
)(builder);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets response meta.
|
||||
*/
|
||||
setResponseMeta() {
|
||||
private setResponseMeta() {
|
||||
this.responseMeta = {
|
||||
filterRoles: this.filterRoles,
|
||||
};
|
||||
|
||||
61
server/src/lib/DynamicFilter/DynamicFilterQueryParser.ts
Normal file
61
server/src/lib/DynamicFilter/DynamicFilterQueryParser.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { OPERATION } from 'lib/LogicEvaluation/Parser';
|
||||
|
||||
export default class QueryParser {
|
||||
|
||||
constructor(tree, queries) {
|
||||
this.tree = tree;
|
||||
this.queries = queries;
|
||||
this.query = null;
|
||||
}
|
||||
|
||||
setQuery(query) {
|
||||
this.query = query.clone();
|
||||
}
|
||||
|
||||
parse() {
|
||||
return this.parseNode(this.tree);
|
||||
}
|
||||
|
||||
parseNode(node) {
|
||||
if (typeof node === 'string') {
|
||||
const nodeQuery = this.getQuery(node);
|
||||
return (query) => { nodeQuery(query); };
|
||||
}
|
||||
if (OPERATION[node.operation] === undefined) {
|
||||
throw new Error(`unknow expression ${node.operation}`);
|
||||
}
|
||||
const leftQuery = this.getQuery(node.left);
|
||||
const rightQuery = this.getQuery(node.right);
|
||||
|
||||
switch (node.operation) {
|
||||
case '&&':
|
||||
case 'AND':
|
||||
default:
|
||||
return (nodeQuery) => nodeQuery.where((query) => {
|
||||
query.where((q) => { leftQuery(q); });
|
||||
query.andWhere((q) => { rightQuery(q); });
|
||||
});
|
||||
case '||':
|
||||
case 'OR':
|
||||
return (nodeQuery) => nodeQuery.where((query) => {
|
||||
query.where((q) => { leftQuery(q); });
|
||||
query.orWhere((q) => { rightQuery(q); });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getQuery(node) {
|
||||
if (typeof node !== 'string' && node !== null) {
|
||||
return this.parseNode(node);
|
||||
}
|
||||
const value = parseFloat(node);
|
||||
|
||||
if (!isNaN(value)) {
|
||||
if (typeof this.queries[node] === 'undefined') {
|
||||
throw new Error(`unknow query under index ${node}`);
|
||||
}
|
||||
return this.queries[node];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,300 @@
|
||||
import { IFilterRole, IDynamicFilter, IModel } from "interfaces";
|
||||
import moment from 'moment';
|
||||
import { IFilterRole, IDynamicFilter, IModel } from 'interfaces';
|
||||
import { Lexer } from 'lib/LogicEvaluation/Lexer';
|
||||
import Parser from 'lib/LogicEvaluation/Parser';
|
||||
import DynamicFilterQueryParser from './DynamicFilterQueryParser';
|
||||
import { COMPARATOR_TYPE, FIELD_TYPE } from './constants';
|
||||
|
||||
export default class DynamicFilterAbstructor implements IDynamicFilter {
|
||||
filterRoles: IFilterRole[] = [];
|
||||
tableName: string;
|
||||
model: IModel;
|
||||
responseMeta: { [key: string]: any } = {};
|
||||
export default abstract class DynamicFilterAbstructor
|
||||
implements IDynamicFilter
|
||||
{
|
||||
protected filterRoles: IFilterRole[] = [];
|
||||
protected tableName: string;
|
||||
protected model: IModel;
|
||||
protected responseMeta: { [key: string]: any } = {};
|
||||
|
||||
setModel(model: IModel) {
|
||||
/**
|
||||
* Sets model the dynamic filter service.
|
||||
* @param {IModel} model
|
||||
*/
|
||||
public setModel(model: IModel) {
|
||||
this.model = model;
|
||||
this.tableName = model.tableName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transformes filter roles to map by index.
|
||||
* @param {IModel} model
|
||||
* @param {IFilterRole[]} roles
|
||||
* @returns
|
||||
*/
|
||||
protected convertRolesMapByIndex = (model, roles) => {
|
||||
const rolesIndexSet = {};
|
||||
|
||||
roles.forEach((role) => {
|
||||
rolesIndexSet[role.index] = this.buildRoleQuery(model, role);
|
||||
});
|
||||
return rolesIndexSet;
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds database query from stored view roles.
|
||||
* @param {Array} roles -
|
||||
* @return {Function}
|
||||
*/
|
||||
protected buildFilterRolesQuery = (
|
||||
model: IModel,
|
||||
roles: IFilterRole[],
|
||||
logicExpression: string = ''
|
||||
) => {
|
||||
const rolesIndexSet = this.convertRolesMapByIndex(model, roles);
|
||||
|
||||
// Lexer for logic expression.
|
||||
const lexer = new Lexer(logicExpression);
|
||||
const tokens = lexer.getTokens();
|
||||
|
||||
// Parse the logic expression.
|
||||
const parser = new Parser(tokens);
|
||||
const parsedTree = parser.parse();
|
||||
|
||||
const queryParser = new DynamicFilterQueryParser(parsedTree, rolesIndexSet);
|
||||
|
||||
return queryParser.parse();
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds filter query for query builder.
|
||||
* @param {String} tableName - Table name.
|
||||
* @param {Array} roles - Filter roles.
|
||||
* @param {String} logicExpression - Logic expression.
|
||||
*/
|
||||
protected buildFilterQuery = (
|
||||
model: IModel,
|
||||
roles: IFilterRole[],
|
||||
logicExpression: string
|
||||
) => {
|
||||
return (builder) => {
|
||||
this.buildFilterRolesQuery(model, roles, logicExpression)(builder);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds roles queries.
|
||||
* @param {IModel} model -
|
||||
* @param {Object} role -
|
||||
*/
|
||||
protected buildRoleQuery = (model: IModel, role: IFilterRole) => {
|
||||
const fieldRelation = model.getField(role.fieldKey);
|
||||
const comparatorColumn = `${model.tableName}.${fieldRelation.column}`;
|
||||
|
||||
// Field relation custom query.
|
||||
if (typeof fieldRelation.customQuery !== 'undefined') {
|
||||
return (builder) => {
|
||||
fieldRelation.customQuery(builder, role);
|
||||
};
|
||||
}
|
||||
switch (fieldRelation.fieldType) {
|
||||
case FIELD_TYPE.BOOLEAN:
|
||||
case FIELD_TYPE.ENUMERATION:
|
||||
return this.booleanRoleQueryBuilder(role, comparatorColumn);
|
||||
case FIELD_TYPE.NUMBER:
|
||||
return this.numberRoleQueryBuilder(role, comparatorColumn);
|
||||
case FIELD_TYPE.DATE:
|
||||
return this.dateQueryBuilder(role, comparatorColumn);
|
||||
case FIELD_TYPE.TEXT:
|
||||
default:
|
||||
return this.textRoleQueryBuilder(role, comparatorColumn);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Boolean column query builder.
|
||||
* @param {IFilterRole} role
|
||||
* @param {string} comparatorColumn
|
||||
* @returns
|
||||
*/
|
||||
protected booleanRoleQueryBuilder = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string
|
||||
) => {
|
||||
switch (role.comparator) {
|
||||
case COMPARATOR_TYPE.EQUALS:
|
||||
case COMPARATOR_TYPE.EQUAL:
|
||||
case COMPARATOR_TYPE.IS:
|
||||
default:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '=', role.value);
|
||||
};
|
||||
case COMPARATOR_TYPE.NOT_EQUAL:
|
||||
case COMPARATOR_TYPE.NOT_EQUALS:
|
||||
case COMPARATOR_TYPE.IS_NOT:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '<>', role.value);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Numeric column query builder.
|
||||
* @param {IFilterRole} role
|
||||
* @param {string} comparatorColumn
|
||||
* @returns
|
||||
*/
|
||||
protected numberRoleQueryBuilder = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string
|
||||
) => {
|
||||
switch (role.comparator) {
|
||||
case COMPARATOR_TYPE.EQUALS:
|
||||
case COMPARATOR_TYPE.EQUAL:
|
||||
default:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '=', role.value);
|
||||
};
|
||||
case COMPARATOR_TYPE.NOT_EQUAL:
|
||||
case COMPARATOR_TYPE.NOT_EQUALS:
|
||||
return (builder) => {
|
||||
builder.whereNot(comparatorColumn, role.value);
|
||||
};
|
||||
case COMPARATOR_TYPE.BIGGER_THAN:
|
||||
case COMPARATOR_TYPE.BIGGER:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '>', role.value);
|
||||
};
|
||||
case COMPARATOR_TYPE.BIGGER_OR_EQUALS:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '>=', role.value);
|
||||
};
|
||||
case COMPARATOR_TYPE.SMALLER_THAN:
|
||||
case COMPARATOR_TYPE.SMALLER:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '<', role.value);
|
||||
};
|
||||
case COMPARATOR_TYPE.SMALLER_OR_EQUALS:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '<=', role.value);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Text column query builder.
|
||||
* @param {IFilterRole} role
|
||||
* @param {string} comparatorColumn
|
||||
* @returns {Function}
|
||||
*/
|
||||
protected textRoleQueryBuilder = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string
|
||||
) => {
|
||||
switch (role.comparator) {
|
||||
case COMPARATOR_TYPE.EQUAL:
|
||||
case COMPARATOR_TYPE.EQUALS:
|
||||
case COMPARATOR_TYPE.IS:
|
||||
default:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, role.value);
|
||||
};
|
||||
case COMPARATOR_TYPE.NOT_EQUALS:
|
||||
case COMPARATOR_TYPE.NOT_EQUAL:
|
||||
case COMPARATOR_TYPE.IS_NOT:
|
||||
return (builder) => {
|
||||
builder.whereNot(comparatorColumn, role.value);
|
||||
};
|
||||
case COMPARATOR_TYPE.CONTAIN:
|
||||
case COMPARATOR_TYPE.CONTAINS:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, 'LIKE', `%${role.value}%`);
|
||||
};
|
||||
case COMPARATOR_TYPE.NOT_CONTAIN:
|
||||
case COMPARATOR_TYPE.NOT_CONTAINS:
|
||||
return (builder) => {
|
||||
builder.whereNot(comparatorColumn, 'LIKE', `%${role.value}%`);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Date column query builder.
|
||||
* @param {IFilterRole} role
|
||||
* @param {string} comparatorColumn
|
||||
* @returns {Function}
|
||||
*/
|
||||
protected dateQueryBuilder = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string
|
||||
) => {
|
||||
switch (role.comparator) {
|
||||
case COMPARATOR_TYPE.AFTER:
|
||||
case COMPARATOR_TYPE.BEFORE:
|
||||
return (builder) => {
|
||||
this.dateQueryAfterBeforeComparator(role, comparatorColumn, builder);
|
||||
};
|
||||
case COMPARATOR_TYPE.IN:
|
||||
return (builder) => {
|
||||
this.dateQueryInComparator(role, comparatorColumn, builder);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Date query 'IN' comparator type.
|
||||
* @param {IFilterRole} role
|
||||
* @param {string} comparatorColumn
|
||||
* @param builder
|
||||
*/
|
||||
protected dateQueryInComparator = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string,
|
||||
builder
|
||||
) => {
|
||||
const hasTimeFormat = moment(
|
||||
role.value,
|
||||
'YYYY-MM-DD HH:MM',
|
||||
true
|
||||
).isValid();
|
||||
const dateFormat = 'YYYY-MM-DD HH:MM:SS';
|
||||
|
||||
if (hasTimeFormat) {
|
||||
const targetDateTime = moment(role.value).format(dateFormat);
|
||||
builder.where(comparatorColumn, '=', targetDateTime);
|
||||
} else {
|
||||
const startDate = moment(role.value).startOf('day');
|
||||
const endDate = moment(role.value).endOf('day');
|
||||
|
||||
builder.where(comparatorColumn, '>=', startDate.format(dateFormat));
|
||||
builder.where(comparatorColumn, '<=', endDate.format(dateFormat));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Date query after/before comparator type.
|
||||
* @param {IFilterRole} role
|
||||
* @param {string} comparatorColumn - Column.
|
||||
* @param builder
|
||||
*/
|
||||
protected dateQueryAfterBeforeComparator = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string,
|
||||
builder
|
||||
) => {
|
||||
const comparator = role.comparator === COMPARATOR_TYPE.BEFORE ? '<' : '>';
|
||||
const hasTimeFormat = moment(
|
||||
role.value,
|
||||
'YYYY-MM-DD HH:MM',
|
||||
true
|
||||
).isValid();
|
||||
const targetDate = moment(role.value);
|
||||
const dateFormat = 'YYYY-MM-DD HH:MM:SS';
|
||||
|
||||
if (!hasTimeFormat) {
|
||||
if (role.comparator === COMPARATOR_TYPE.BEFORE) {
|
||||
targetDate.startOf('day');
|
||||
} else {
|
||||
targetDate.endOf('day');
|
||||
}
|
||||
}
|
||||
const comparatorValue = targetDate.format(dateFormat);
|
||||
builder.where(comparatorColumn, comparator, comparatorValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,13 @@ import {
|
||||
getTableFromRelationColumn,
|
||||
} from 'lib/ViewRolesBuilder';
|
||||
|
||||
interface ISortRole {
|
||||
fieldKey: string;
|
||||
order: string;
|
||||
}
|
||||
|
||||
export default class DynamicFilterSortBy extends DynamicFilterRoleAbstructor {
|
||||
sortRole: { fieldKey: string; order: string } = {};
|
||||
private sortRole: ISortRole = {};
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
@@ -23,39 +28,28 @@ export default class DynamicFilterSortBy extends DynamicFilterRoleAbstructor {
|
||||
this.setResponseMeta();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the given field key with the model.
|
||||
*/
|
||||
validate() {
|
||||
validateFieldKeyExistance(this.model, this.sortRole.fieldKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds database query of sort by column on the given direction.
|
||||
*/
|
||||
buildQuery() {
|
||||
const fieldRelation = getRoleFieldColumn(
|
||||
this.model,
|
||||
this.sortRole.fieldKey
|
||||
);
|
||||
const comparatorColumn =
|
||||
fieldRelation.relationColumn ||
|
||||
`${this.tableName}.${fieldRelation.column}`;
|
||||
public buildQuery() {
|
||||
const field = this.model.getField(this.sortRole.fieldKey);
|
||||
const comparatorColumn = `${this.tableName}.${field.column}`;
|
||||
|
||||
if (typeof fieldRelation.sortQuery !== 'undefined') {
|
||||
if (typeof field.customSortQuery !== 'undefined') {
|
||||
return (builder) => {
|
||||
fieldRelation.sortQuery(builder, this.sortRole);
|
||||
field.customSortQuery(builder, this.sortRole);
|
||||
};
|
||||
}
|
||||
|
||||
return (builder) => {
|
||||
if (this.sortRole.fieldKey) {
|
||||
builder.orderBy(`${comparatorColumn}`, this.sortRole.order);
|
||||
}
|
||||
this.joinBuildQuery()(builder);
|
||||
};
|
||||
}
|
||||
|
||||
joinBuildQuery() {
|
||||
private joinBuildQuery() {
|
||||
const fieldColumn = getRoleFieldColumn(this.model, this.sortRole.fieldKey);
|
||||
|
||||
return (builder) => {
|
||||
@@ -75,7 +69,7 @@ export default class DynamicFilterSortBy extends DynamicFilterRoleAbstructor {
|
||||
/**
|
||||
* Sets response meta.
|
||||
*/
|
||||
setResponseMeta() {
|
||||
public setResponseMeta() {
|
||||
this.responseMeta = {
|
||||
sortOrder: this.sortRole.fieldKey,
|
||||
sortBy: this.sortRole.order,
|
||||
|
||||
36
server/src/lib/DynamicFilter/constants.ts
Normal file
36
server/src/lib/DynamicFilter/constants.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
export const COMPARATOR_TYPE = {
|
||||
EQUAL: 'equal',
|
||||
EQUALS: 'equals',
|
||||
|
||||
NOT_EQUAL: 'not_equal',
|
||||
NOT_EQUALS: 'not_equals',
|
||||
|
||||
BIGGER_THAN: 'bigger_than',
|
||||
BIGGER: 'bigger',
|
||||
BIGGER_OR_EQUALS: 'bigger_or_equals',
|
||||
|
||||
SMALLER_THAN: 'smaller_than',
|
||||
SMALLER: 'smaller',
|
||||
SMALLER_OR_EQUALS: 'smaller_or_equals',
|
||||
|
||||
IS: 'is',
|
||||
IS_NOT: 'is_not',
|
||||
|
||||
CONTAINS: 'contains',
|
||||
CONTAIN: 'contain',
|
||||
NOT_CONTAINS: 'contains',
|
||||
NOT_CONTAIN: 'contain',
|
||||
|
||||
AFTER: 'after',
|
||||
BEFORE: 'before',
|
||||
IN: 'in',
|
||||
};
|
||||
|
||||
export const FIELD_TYPE = {
|
||||
TEXT: 'text',
|
||||
NUMBER: 'number',
|
||||
ENUMERATION: 'enumeration',
|
||||
BOOLEAN: 'boolean',
|
||||
RELATION: 'relation',
|
||||
DATE: 'date',
|
||||
};
|
||||
@@ -1,121 +1,7 @@
|
||||
import { difference } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { Lexer } from 'lib/LogicEvaluation/Lexer';
|
||||
import Parser from 'lib/LogicEvaluation/Parser';
|
||||
import QueryParser from 'lib/LogicEvaluation/QueryParser';
|
||||
|
||||
import { IFilterRole, IModel } from 'interfaces';
|
||||
|
||||
const numberRoleQueryBuilder = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string
|
||||
) => {
|
||||
switch (role.comparator) {
|
||||
case 'equals':
|
||||
case 'equal':
|
||||
default:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '=', role.value);
|
||||
};
|
||||
case 'not_equals':
|
||||
case 'not_equal':
|
||||
return (builder) => {
|
||||
builder.whereNot(comparatorColumn, role.value);
|
||||
};
|
||||
case 'bigger_than':
|
||||
case 'bigger':
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '>', role.value);
|
||||
};
|
||||
case 'bigger_or_equals':
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '>=', role.value);
|
||||
};
|
||||
case 'smaller_than':
|
||||
case 'smaller':
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '<', role.value);
|
||||
};
|
||||
case 'smaller_or_equals':
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, '<=', role.value);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const textRoleQueryBuilder = (role: IFilterRole, comparatorColumn: string) => {
|
||||
switch (role.comparator) {
|
||||
case 'equals':
|
||||
case 'is':
|
||||
default:
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, role.value);
|
||||
};
|
||||
case 'not_equal':
|
||||
case 'not_equals':
|
||||
case 'is_not':
|
||||
return (builder) => {
|
||||
builder.whereNot(comparatorColumn, role.value);
|
||||
};
|
||||
case 'contain':
|
||||
case 'contains':
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, 'LIKE', `%${role.value}%`);
|
||||
};
|
||||
case 'not_contain':
|
||||
case 'not_contains':
|
||||
return (builder) => {
|
||||
builder.whereNot(comparatorColumn, 'LIKE', `%${role.value}%`);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const dateQueryBuilder = (role: IFilterRole, comparatorColumn: string) => {
|
||||
switch (role.comparator) {
|
||||
case 'after':
|
||||
case 'before':
|
||||
return (builder) => {
|
||||
const comparator = role.comparator === 'before' ? '<' : '>';
|
||||
const hasTimeFormat = moment(
|
||||
role.value,
|
||||
'YYYY-MM-DD HH:MM',
|
||||
true
|
||||
).isValid();
|
||||
const targetDate = moment(role.value);
|
||||
const dateFormat = 'YYYY-MM-DD HH:MM:SS';
|
||||
|
||||
if (!hasTimeFormat) {
|
||||
if (role.comparator === 'before') {
|
||||
targetDate.startOf('day');
|
||||
} else {
|
||||
targetDate.endOf('day');
|
||||
}
|
||||
}
|
||||
const comparatorValue = targetDate.format(dateFormat);
|
||||
builder.where(comparatorColumn, comparator, comparatorValue);
|
||||
};
|
||||
case 'in':
|
||||
return (builder) => {
|
||||
const hasTimeFormat = moment(
|
||||
role.value,
|
||||
'YYYY-MM-DD HH:MM',
|
||||
true
|
||||
).isValid();
|
||||
const dateFormat = 'YYYY-MM-DD HH:MM:SS';
|
||||
|
||||
if (hasTimeFormat) {
|
||||
const targetDateTime = moment(role.value).format(dateFormat);
|
||||
builder.where(comparatorColumn, '=', targetDateTime);
|
||||
} else {
|
||||
const startDate = moment(role.value).startOf('day');
|
||||
const endDate = moment(role.value).endOf('day');
|
||||
|
||||
builder.where(comparatorColumn, '>=', startDate.format(dateFormat));
|
||||
builder.where(comparatorColumn, '<=', endDate.format(dateFormat));
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get field column metadata and its relation with other tables.
|
||||
* @param {String} tableName - Table name of target column.
|
||||
@@ -126,68 +12,6 @@ export function getRoleFieldColumn(model: IModel, fieldKey: string) {
|
||||
return tableFields[fieldKey] ? tableFields[fieldKey] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds roles queries.
|
||||
* @param {IModel} model -
|
||||
* @param {Object} role -
|
||||
*/
|
||||
export function buildRoleQuery(model: IModel, role: IFilterRole) {
|
||||
const fieldRelation = getRoleFieldColumn(model, role.fieldKey);
|
||||
const comparatorColumn =
|
||||
fieldRelation.relationColumn ||
|
||||
`${model.tableName}.${fieldRelation.column}`;
|
||||
|
||||
if (typeof fieldRelation.query !== 'undefined') {
|
||||
return (builder) => {
|
||||
fieldRelation.query(builder, role);
|
||||
};
|
||||
}
|
||||
switch (fieldRelation.columnType) {
|
||||
case 'number':
|
||||
return numberRoleQueryBuilder(role, comparatorColumn);
|
||||
case 'date':
|
||||
return dateQueryBuilder(role, comparatorColumn);
|
||||
case 'text':
|
||||
case 'varchar':
|
||||
default:
|
||||
return textRoleQueryBuilder(role, comparatorColumn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract relation table name from relation.
|
||||
* @param {String} column -
|
||||
* @return {String} - join relation table.
|
||||
*/
|
||||
export const getTableFromRelationColumn = (column: string) => {
|
||||
const splitedColumn = column.split('.');
|
||||
return splitedColumn.length > 0 ? splitedColumn[0] : '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds view roles join queries.
|
||||
* @param {String} tableName - Table name.
|
||||
* @param {Array} roles - Roles.
|
||||
*/
|
||||
export function buildFilterRolesJoins(model: IModel, roles: IFilterRole[]) {
|
||||
return (builder) => {
|
||||
roles.forEach((role) => {
|
||||
const fieldColumn = getRoleFieldColumn(model, role.fieldKey);
|
||||
|
||||
if (fieldColumn.relation) {
|
||||
const joinTable = getTableFromRelationColumn(fieldColumn.relation);
|
||||
|
||||
builder.join(
|
||||
joinTable,
|
||||
`${model.tableName}.${fieldColumn.column}`,
|
||||
'=',
|
||||
fieldColumn.relation
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function buildSortColumnJoin(model: IModel, sortColumnKey: string) {
|
||||
return (builder) => {
|
||||
const fieldColumn = getRoleFieldColumn(model, sortColumnKey);
|
||||
@@ -204,50 +28,6 @@ export function buildSortColumnJoin(model: IModel, sortColumnKey: string) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds database query from stored view roles.
|
||||
*
|
||||
* @param {Array} roles -
|
||||
* @return {Function}
|
||||
*/
|
||||
export function buildFilterRolesQuery(
|
||||
model: IModel,
|
||||
roles: IFilterRole[],
|
||||
logicExpression: string = ''
|
||||
) {
|
||||
const rolesIndexSet = {};
|
||||
|
||||
roles.forEach((role) => {
|
||||
rolesIndexSet[role.index] = buildRoleQuery(model, role);
|
||||
});
|
||||
// Lexer for logic expression.
|
||||
const lexer = new Lexer(logicExpression);
|
||||
const tokens = lexer.getTokens();
|
||||
|
||||
// Parse the logic expression.
|
||||
const parser = new Parser(tokens);
|
||||
const parsedTree = parser.parse();
|
||||
|
||||
const queryParser = new QueryParser(parsedTree, rolesIndexSet);
|
||||
return queryParser.parse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds filter query for query builder.
|
||||
* @param {String} tableName -
|
||||
* @param {Array} roles -
|
||||
* @param {String} logicExpression -
|
||||
*/
|
||||
export const buildFilterQuery = (
|
||||
model: IModel,
|
||||
roles: IFilterRole[],
|
||||
logicExpression: string
|
||||
) => {
|
||||
return (builder) => {
|
||||
buildFilterRolesQuery(model, roles, logicExpression)(builder);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Mapes the view roles to view conditionals.
|
||||
* @param {Array} viewRoles -
|
||||
@@ -316,14 +96,6 @@ export function validateFieldKeyExistance(model: any, fieldKey: string) {
|
||||
return model?.fields?.[fieldKey] || false;
|
||||
}
|
||||
|
||||
export function validateFilterRolesFieldsExistance(
|
||||
model,
|
||||
filterRoles: IFilterRole[]
|
||||
) {
|
||||
return filterRoles.filter((filterRole: IFilterRole) => {
|
||||
return !validateFieldKeyExistance(model, filterRole.fieldKey);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve model fields keys.
|
||||
|
||||
Reference in New Issue
Block a user