Files
bigcapital/server/src/lib/ViewRolesBuilder/index.ts
Ahmed Bouhuolia 27ec0e91fa refactoring: retrieve resources fields.
fix: issue in create a new custom view.
2020-10-17 15:00:22 +02:00

299 lines
8.7 KiB
TypeScript

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':
default:
return (builder) => {
builder.where(comparatorColumn, role.value);
};
case 'not_equal':
case 'not_equals':
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.
* @param {String} fieldKey - Target column key that stored in resource field.
*/
export function getRoleFieldColumn(model: IModel, fieldKey: string) {
const tableFields = model.fields;
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}`;
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 -
* @param {Array} 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);
if (fieldColumn.relation) {
const joinTable = getTableFromRelationColumn(fieldColumn.relation);
builder.join(joinTable, `${model.tableName}.${fieldColumn.column}`, '=', fieldColumn.relation);
}
};
}
/**
* 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 -
* @return {Array}
*/
export function mapViewRolesToConditionals(viewRoles) {
return viewRoles.map((viewRole) => ({
comparator: viewRole.comparator,
value: viewRole.value,
index: viewRole.index,
columnKey: viewRole.field.key,
slug: viewRole.field.slug,
}));
}
export function mapFilterRolesToDynamicFilter(roles) {
return roles.map((role) => ({
...role,
columnKey: role.fieldKey,
}));
}
/**
* Builds sort column query.
* @param {String} tableName -
* @param {String} columnKey -
* @param {String} sortDirection -
*/
export function buildSortColumnQuery(model: IModel, columnKey: string, sortDirection: string) {
const fieldRelation = getRoleFieldColumn(model, columnKey);
const sortColumn = fieldRelation.relation || `${model.tableName}.${fieldRelation.column}`;
return (builder) => {
builder.orderBy(sortColumn, sortDirection);
buildSortColumnJoin(model, columnKey)(builder);
};
}
export function validateFilterLogicExpression(logicExpression: string, indexes) {
const logicExpIndexes = logicExpression.match(/\d+/g) || [];
const diff = difference(logicExpIndexes.map(Number), indexes);
return (diff.length > 0) ? false : true;
}
export function validateRolesLogicExpression(logicExpression: string, roles: IFilterRole[]) {
return validateFilterLogicExpression(logicExpression, roles.map((r) => r.index));
}
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.
* @param {IModel} Model
* @return {string[]}
*/
export function getModelFieldsKeys(Model: IModel) {
const fields = Object.keys(Model.fields);
return fields.sort((a, b) => {
if (a < b) { return -1; }
if (a > b) { return 1; }
return 0;
});
}
export function getModelFields(Model: IModel) {
const fieldsKey = this.getModelFieldsKeys(Model);
return fieldsKey.map((fieldKey) => {
const field = Model.fields[fieldKey];
return {
...field,
key: fieldKey,
};
})
}