budibase/packages/core/src/templateApi/createNodes.js

243 lines
5.9 KiB
JavaScript
Raw Normal View History

import { each, find } from "lodash"
import { map, max, constant } from "lodash/fp"
2019-07-15 08:12:52 +02:00
import {
switchCase,
defaultCase,
joinKey,
$,
isNothing,
isSomething,
} from "../common"
2019-07-15 08:12:52 +02:00
import {
isIndex,
isRoot,
isSingleRecord,
isCollectionRecord,
isRecord,
isaggregateGroup,
2019-07-15 08:12:52 +02:00
getFlattenedHierarchy,
} from "./hierarchy"
import { all } from "../types"
import { BadRequestError } from "../common/errors"
2019-07-15 08:12:52 +02:00
export const createNodeErrors = {
indexCannotBeParent: "Index template cannot be a parent",
allNonRootNodesMustHaveParent: "Only the root node may have no parent",
indexParentMustBeRecordOrRoot:
"An index may only have a record or root as a parent",
aggregateParentMustBeAnIndex: "aggregateGroup parent must be an index",
}
const pathRegxMaker = node => () =>
node.nodeKey().replace(/{id}/g, "[a-zA-Z0-9_-]+")
const nodeKeyMaker = node => () =>
switchCase(
[
n => isRecord(n) && !isSingleRecord(n),
n =>
joinKey(
node.parent().nodeKey(),
node.collectionName,
`${n.nodeId}-{id}`
),
],
[isRoot, constant("/")],
[defaultCase, n => joinKey(node.parent().nodeKey(), n.name)]
)(node)
const nodeNameMaker = node => () =>
isRoot(node)
? "/"
: joinKey(node.parent().nodeName(), node.name)
const validate = parent => node => {
if (
isIndex(node) &&
isSomething(parent) &&
!isRoot(parent) &&
!isRecord(parent)
) {
throw new BadRequestError(createNodeErrors.indexParentMustBeRecordOrRoot)
2019-07-15 08:12:52 +02:00
}
if (isaggregateGroup(node) && isSomething(parent) && !isIndex(parent)) {
throw new BadRequestError(createNodeErrors.aggregateParentMustBeAnIndex)
2019-07-15 08:12:52 +02:00
}
if (isNothing(parent) && !isRoot(node)) {
throw new BadRequestError(createNodeErrors.allNonRootNodesMustHaveParent)
}
2019-07-15 08:12:52 +02:00
return node
}
2019-07-15 08:12:52 +02:00
const construct = parent => node => {
node.nodeKey = nodeKeyMaker(node)
node.nodeName = nodeNameMaker(node)
node.pathRegx = pathRegxMaker(node)
node.parent = constant(parent)
node.isRoot = () =>
isNothing(parent) && node.name === "root" && node.type === "root"
2019-07-15 08:12:52 +02:00
if (isCollectionRecord(node)) {
node.collectionNodeKey = () =>
joinKey(parent.nodeKey(), node.collectionName)
node.collectionPathRegx = () =>
joinKey(parent.pathRegx(), node.collectionName)
2019-07-15 08:12:52 +02:00
}
return node
}
2019-07-15 08:12:52 +02:00
const addToParent = obj => {
const parent = obj.parent()
2019-07-15 08:12:52 +02:00
if (isSomething(parent)) {
if (isIndex(obj)) {
// Q: why are indexes not children ?
// A: because they cannot have children of their own.
parent.indexes.push(obj)
} else if (isaggregateGroup(obj)) {
parent.aggregateGroups.push(obj)
} else {
parent.children.push(obj)
2019-10-31 10:22:46 +01:00
}
2019-07-15 08:12:52 +02:00
if (isRecord(obj)) {
const defaultIndex = find(
parent.indexes,
i => i.name === `${parent.name}_index`
)
2019-07-15 08:12:52 +02:00
if (defaultIndex) {
defaultIndex.allowedRecordNodeIds.push(obj.nodeId)
2019-07-15 08:12:52 +02:00
}
}
}
return obj
}
2019-07-15 08:12:52 +02:00
export const constructNode = (parent, obj) =>
$(obj, [construct(parent), validate(parent), addToParent])
2019-07-15 08:12:52 +02:00
const getNodeId = parentNode => {
2019-07-15 08:12:52 +02:00
// this case is handled better elsewhere
if (!parentNode) return null
const findRoot = n => (isRoot(n) ? n : findRoot(n.parent()))
const root = findRoot(parentNode)
2019-07-15 08:12:52 +02:00
return $(root, [getFlattenedHierarchy, map(n => n.nodeId), max]) + 1
}
2019-07-15 08:12:52 +02:00
export const constructHierarchy = (node, parent) => {
construct(parent)(node)
2019-07-15 08:12:52 +02:00
if (node.indexes) {
each(node.indexes, child => constructHierarchy(child, node))
2019-07-15 08:12:52 +02:00
}
if (node.aggregateGroups) {
each(node.aggregateGroups, child => constructHierarchy(child, node))
2019-07-15 08:12:52 +02:00
}
if (node.children && node.children.length > 0) {
each(node.children, child => constructHierarchy(child, node))
2019-07-15 08:12:52 +02:00
}
if (node.fields) {
each(node.fields, f =>
each(f.typeOptions, (val, key) => {
const def = all[f.type].optionDefinitions[key]
2019-07-15 08:12:52 +02:00
if (!def) {
// unknown typeOption
delete f.typeOptions[key]
2019-07-15 08:12:52 +02:00
} else {
f.typeOptions[key] = def.parse(val)
2019-07-15 08:12:52 +02:00
}
})
)
2019-07-15 08:12:52 +02:00
}
return node
}
2019-07-15 08:12:52 +02:00
export const getNewRootLevel = () =>
construct()({
name: "root",
type: "root",
children: [],
pathMaps: [],
indexes: [],
nodeId: 0,
})
2019-07-15 08:12:52 +02:00
const _getNewRecordTemplate = (parent, name, createDefaultIndex, isSingle) => {
const nodeId = getNodeId(parent)
2019-07-15 08:12:52 +02:00
const node = constructNode(parent, {
name,
type: "record",
2019-07-15 08:12:52 +02:00
fields: [],
children: [],
validationRules: [],
nodeId: nodeId,
2019-07-15 08:12:52 +02:00
indexes: [],
estimatedRecordCount: isRecord(parent) ? 500 : 1000000,
collectionName: (nodeId || "").toString(),
2019-07-15 08:12:52 +02:00
isSingle,
})
2019-07-15 08:12:52 +02:00
if (createDefaultIndex) {
const defaultIndex = getNewIndexTemplate(parent)
defaultIndex.name = `${name}_index`
defaultIndex.allowedRecordNodeIds.push(node.nodeId)
2019-07-15 08:12:52 +02:00
}
return node
}
export const getNewRecordTemplate = (
parent,
name = "",
createDefaultIndex = true
) => _getNewRecordTemplate(parent, name, createDefaultIndex, false)
export const getNewSingleRecordTemplate = parent =>
_getNewRecordTemplate(parent, "", false, true)
export const getNewIndexTemplate = (parent, type = "ancestor") =>
constructNode(parent, {
name: "",
type: "index",
map: "return {...record};",
filter: "",
indexType: type,
getShardName: "",
getSortKey: "record.id",
aggregateGroups: [],
allowedRecordNodeIds: [],
nodeId: getNodeId(parent),
})
export const getNewAggregateGroupTemplate = index =>
constructNode(index, {
name: "",
type: "aggregateGroup",
groupBy: "",
aggregates: [],
condition: "",
nodeId: getNodeId(index),
})
export const getNewAggregateTemplate = set => {
2019-07-15 08:12:52 +02:00
const aggregatedValue = {
name: "",
aggregatedValue: "",
}
set.aggregates.push(aggregatedValue)
return aggregatedValue
}
2019-07-15 08:12:52 +02:00
export default {
getNewRootLevel,
getNewRecordTemplate,
getNewIndexTemplate,
createNodeErrors,
constructHierarchy,
getNewAggregateGroupTemplate,
getNewAggregateTemplate,
}