1
0
Fork 0
mirror of synced 2024-09-28 23:31:43 +12:00
budibase/packages/server/src/api/controllers/view/viewBuilder.js

130 lines
3 KiB
JavaScript
Raw Normal View History

2020-08-21 03:31:43 +12:00
const TOKEN_MAP = {
EQUALS: "===",
LT: "<",
LTE: "<=",
MT: ">",
MTE: ">=",
CONTAINS: "includes",
2020-08-21 03:31:43 +12:00
AND: "&&",
OR: "||",
2020-08-21 03:31:43 +12:00
}
const GROUP_PROPERTY = {
group: {
type: "string",
},
}
const FIELD_PROPERTY = {
field: {
type: "string",
},
}
2020-08-24 22:46:28 +12:00
const SCHEMA_MAP = {
stats: {
2020-08-25 02:48:34 +12:00
sum: {
type: "number",
2020-08-24 22:46:28 +12:00
},
2020-08-25 02:48:34 +12:00
min: {
type: "number",
2020-08-24 22:46:28 +12:00
},
2020-08-25 02:48:34 +12:00
max: {
type: "number",
2020-08-24 22:46:28 +12:00
},
2020-08-25 02:48:34 +12:00
count: {
type: "number",
2020-08-24 22:46:28 +12:00
},
2020-08-25 02:48:34 +12:00
sumsqr: {
type: "number",
2020-08-24 22:46:28 +12:00
},
2020-08-25 02:48:34 +12:00
avg: {
type: "number",
},
},
2020-08-24 22:46:28 +12:00
}
/**
* Iterates through the array of filters to create a JS
* expression that gets used in a CouchDB view.
* @param {Array} filters - an array of filter objects
* @returns {String} JS Expression
*/
function parseFilterExpression(filters) {
const expression = []
for (let filter of filters) {
2020-08-25 02:48:34 +12:00
if (filter.conjunction) expression.push(TOKEN_MAP[filter.conjunction])
if (filter.condition === "CONTAINS") {
expression.push(
2020-08-25 02:48:34 +12:00
`doc["${filter.key}"].${TOKEN_MAP[filter.condition]}("${filter.value}")`
)
} else {
expression.push(
`doc["${filter.key}"] ${TOKEN_MAP[filter.condition]} "${filter.value}"`
)
}
}
2020-08-21 03:31:43 +12:00
return expression.join(" ")
}
2020-08-25 02:48:34 +12:00
/**
* Returns a CouchDB compliant emit() expression that is used to emit the
* correct key/value pairs for custom views.
* @param {String?} field - field to use for calculations, if any
* @param {String?} groupBy - field to group calculation results on, if any
*/
function parseEmitExpression(field, groupBy) {
if (field) return `emit(doc["${groupBy || "_id"}"], doc["${field}"]);`
return `emit(doc._id);`
}
2020-08-25 02:48:34 +12:00
/**
* Return a fully parsed CouchDB compliant view definition
* that will be stored in the design document in the database.
*
* @param {Object} viewDefinition - the JSON definition for a custom view.
* field: field that calculations will be performed on
* modelId: modelId of the model this view was created from
* groupBy: field that calculations will be grouped by. Field must be present for this to be useful
* filters: Array of filter objects containing predicates that are parsed into a JS expression
* calculation: an optional calculation to be performed over the view data.
*/
function viewTemplate({ field, modelId, groupBy, filters = [], calculation }) {
const parsedFilters = parseFilterExpression(filters)
2020-08-24 22:46:28 +12:00
const filterExpression = parsedFilters ? `&& ${parsedFilters}` : ""
const emitExpression = parseEmitExpression(field, groupBy)
2020-08-24 22:46:28 +12:00
const reduction = field ? { reduce: "_stats" } : {}
let schema = null
if (calculation) {
schema = groupBy
? { ...GROUP_PROPERTY, ...SCHEMA_MAP[calculation] }
: { ...FIELD_PROPERTY, ...SCHEMA_MAP[calculation] }
}
2020-08-15 03:31:53 +12:00
return {
meta: {
field,
modelId,
groupBy,
filters,
schema,
2020-08-25 02:48:34 +12:00
calculation,
2020-08-15 03:31:53 +12:00
},
map: `function (doc) {
2020-08-24 22:46:28 +12:00
if (doc.modelId === "${modelId}" ${filterExpression}) {
${emitExpression}
2020-08-15 03:31:53 +12:00
}
}`,
2020-08-25 02:48:34 +12:00
...reduction,
2020-08-15 03:31:53 +12:00
}
}
2020-08-24 22:46:28 +12:00
module.exports = viewTemplate