cheetah-framework
Version:
Cheetah Framework JS used in all our applications
324 lines (286 loc) • 8.83 kB
JavaScript
import { Model } from '@cheetah/models/Model'
import Criteria from '@cheetah/models/Criteria'
import BaseValueComponent from '@cheetah/components/QueryBuilder/value/Base'
import ArrayableValueComponent from '@cheetah/components/QueryBuilder/value/Arrayable'
import AggregatorValueComponent from '@cheetah/components/QueryBuilder/value/Aggregator'
const BUILDER_OPERATORS = {
EQUAL: '=',
NOT_EQUAL: '<>',
LIKE: 'like',
NOT_LIKE: 'not_like',
IN: 'in',
NOT_IN: 'not_in',
LT: '<',
LTE: '<=',
GT: '>',
GTE: '>=',
NULL: 'null',
NOT_NULL: 'not_null',
EXIST: 'exist',
NOT_EXIST: 'not_exist',
COUNT: 'count',
MAX: 'max',
MIN: 'min'
}
const RELATION_TYPE_OPERATORS = {
WITH: 'with',
WITHOUT: 'without'
}
const operatorToValueComponentMap = {
[BUILDER_OPERATORS.EQUAL]: BaseValueComponent,
[BUILDER_OPERATORS.NOT_EQUAL]: BaseValueComponent,
[BUILDER_OPERATORS.LIKE]: BaseValueComponent,
[BUILDER_OPERATORS.NOT_LIKE]: BaseValueComponent,
[BUILDER_OPERATORS.IN]: ArrayableValueComponent,
[BUILDER_OPERATORS.NOT_IN]: ArrayableValueComponent,
[BUILDER_OPERATORS.LT]: BaseValueComponent,
[BUILDER_OPERATORS.LTE]: BaseValueComponent,
[BUILDER_OPERATORS.GT]: BaseValueComponent,
[BUILDER_OPERATORS.GTE]: BaseValueComponent,
[BUILDER_OPERATORS.NULL]: null,
[BUILDER_OPERATORS.NOT_NULL]: null,
[BUILDER_OPERATORS.COUNT]: AggregatorValueComponent,
[BUILDER_OPERATORS.EXIST]: null,
[BUILDER_OPERATORS.NOT_EXIST]: null,
[BUILDER_OPERATORS.MAX]: AggregatorValueComponent,
[BUILDER_OPERATORS.MIN]: AggregatorValueComponent
}
class Criterion extends Model {
constructor (data) {
super(data)
this.initBuilderFields()
}
/**
* Default values should consist of every possible key:value
* a criterion can have to ensure the reactivity is
* properly bound the every props.
*
* @return {object}
*/
get defaultValue () {
return {
/**
* Aggregation field key
*/
relation: null,
/**
* Criterion field key
*/
field: null,
/**
* Operator for simple criterion OR operator for criteria
* if the current criterion is an aggregator.
*/
operator: null,
/**
* Comparison value for simple criterion
*/
value: null,
/**
* Comparison column for criterion comparing
* two fields
*/
value_of: null,
/**
* List of aggregation conditions.Support only
* a single condition.
*/
aggregators: [{
/**
* Aggregation type
*/
aggregation: null,
/**
* Aggregated column
*/
field: null,
/**
* Aggregation operator for comparison
*/
operator: null,
/**
* Aggregation value to compare
*/
value: null
}],
/**
* Array of criteria for an aggregation. Expect to receive an array
* of criteria that will be converted to:
*
* {
* operator: {string},
* criteria: {Criteria[]|Criterion[]}
* }
*/
criteria: [],
/**
* Props used by the query builder
*/
builder_operator: null,
builder_compare_field: false,
is_editing: false
}
}
/**
* Set the criterion builder operator which will, in the end,
* define the criterion structure.
*
* @param value
*/
setBuilderOperatorAttribute (value) {
this._attributes.builder_operator = value
}
/**
* Guesses builder fields based on received data. This function should be
* called only on new criterion instances.
*
* Should basically be reverting what this.toJSON() does.
*/
initBuilderFields () {
if (this.value_of) {
this.builder_compare_field = true
}
if (_.includes([BUILDER_OPERATORS.LIKE, BUILDER_OPERATORS.NOT_LIKE], this.operator)) {
/**
* Fail safely check if the value ends or starts
* with "%" before doing an operation.
*/
if (_.startsWith(this.value, '%') && _.endsWith(this.value, '%')) {
this.value = this.value.slice(1, -1)
}
}
if (this.aggregators[0].aggregation) {
this.builder_operator = this.aggregators[0].aggregation
this.field = this.relation
this.criteria = _.pick(this, ['operator', 'criteria'])
} else if (_.includes(_.values(RELATION_TYPE_OPERATORS), this.type)) {
this.field = this.relation
this.builder_operator = BUILDER_OPERATORS[this.type === RELATION_TYPE_OPERATORS.WITH ? 'EXIST' : 'NOT_EXIST']
this.criteria = _.pick(this, ['operator', 'criteria'])
} else {
this.criteria = {
operator: Criteria.OPERATORS.AND,
criteria: []
}
this.builder_operator = this.operator
}
}
get valueComponent () {
return operatorToValueComponentMap[this.builder_operator]
}
get isAggregation () {
return _.includes(_.pick(BUILDER_OPERATORS, ['COUNT', 'MAX', 'MIN']), this.builder_operator)
}
get hasSubQuery () {
return this.isAggregation || _.includes(_.pick(BUILDER_OPERATORS, ['EXIST', 'NOT_EXIST']), this.builder_operator)
}
/**
* is_editing getter used by the query builder.
* Use native getter to prevent sending it in the payload
*
* @return {boolean}
*/
get is_editing () {
return this._attributes.is_editing
}
/**
* is_editing setter used by the query builder.
*
* @param {boolean} value
*/
set is_editing (value) {
this._attributes.is_editing = value
}
/**
* Parse builder_* fields to get the true
* criterion structure.
*
* @return {object}
*/
toJSON () {
const baseObject = _.cloneDeep(super.toJSON())
if (this.isAggregation) {
if (baseObject.builder_operator === BUILDER_OPERATORS.COUNT) {
_.unset(baseObject, 'aggregators.0.field')
}
_.set(baseObject, 'aggregators.0.aggregation', baseObject.builder_operator)
return {
relation: baseObject.field,
..._.pick(baseObject, ['aggregators']),
...baseObject.criteria
}
}
if (_.includes(_.pick(BUILDER_OPERATORS, ['EXIST', 'NOT_EXIST']), baseObject.builder_operator)) {
switch (baseObject.builder_operator) {
case BUILDER_OPERATORS.EXIST:
_.set(baseObject, 'type', RELATION_TYPE_OPERATORS.WITH)
_.set(baseObject, 'value', BUILDER_OPERATORS.EXIST)
break
case BUILDER_OPERATORS.NOT_EXIST:
_.set(baseObject, 'type', RELATION_TYPE_OPERATORS.WITHOUT)
_.set(baseObject, 'value', BUILDER_OPERATORS.EXIST)
break
}
return {
relation: baseObject.field,
..._.pick(baseObject, ['type', 'value']),
...baseObject.criteria
}
}
if (baseObject.builder_compare_field) {
return {
operator: baseObject.builder_operator,
..._.pick(baseObject, ['field', 'value_of', 'builder_compare_field'])
}
}
if (_.includes([BUILDER_OPERATORS.LIKE, BUILDER_OPERATORS.NOT_LIKE], baseObject.builder_operator)) {
return {
operator: baseObject.builder_operator,
field: baseObject.field,
value: `%${baseObject.value}%`
}
}
return {
operator: baseObject.builder_operator,
..._.pick(baseObject, ['field', 'value'])
}
}
static getOperatorsFor (fieldType) {
let operators = []
switch (fieldType) {
case 'string' :
operators = ['EQUAL', 'NOT_EQUAL', 'LIKE', 'NOT_LIKE', 'IN', 'NOT_IN', 'NULL', 'NOT_NULL']
break
case 'boolean' :
operators = ['EQUAL', 'NOT_EQUAL', 'NULL', 'NOT_NULL']
break
case 'enum' :
operators = ['EQUAL', 'NOT_EQUAL', 'IN', 'NOT_IN', 'NULL', 'NOT_NULL']
break
case 'relation' :
operators = ['EXIST', 'NOT_EXIST', 'COUNT']
break
case 'integer' :
case 'float' :
case 'decimal' :
operators = ['EQUAL', 'NOT_EQUAL', 'LT', 'LTE', 'GT', 'GTE', 'IN', 'NOT_IN', 'NULL', 'NOT_NULL']
break
case 'date':
case 'datetime':
operators = ['EQUAL', 'NOT_EQUAL', 'LT', 'LTE', 'GT', 'GTE', 'NULL', 'NOT_NULL']
break
case 'model' :
operators = ['EQUAL', 'NOT_EQUAL', 'IN', 'NOT_IN', 'NULL', 'NOT_NULL']
break
case 'aggregation':
operators = ['EQUAL', 'NOT_EQUAL', 'LT', 'LTE', 'GT', 'GTE']
break
}
return _.values(_.pick(Criterion.BUILDER_OPERATORS, operators))
}
}
Criterion.BUILDER_OPERATORS = BUILDER_OPERATORS
Criterion.RELATION_TYPE_OPERATORS = RELATION_TYPE_OPERATORS
Criterion.allBuilderOperators = _.values(BUILDER_OPERATORS)
Criterion.arrayableBuilderOperators = _.values(_.pick(BUILDER_OPERATORS, ['IN', 'NOT_IN']))
export default Criterion