2020-02-03 10:24:25 +01:00
|
|
|
import { each, find } from "lodash"
|
|
|
|
import { map, max, constant } from "lodash/fp"
|
2019-07-15 08:12:52 +02:00
|
|
|
import {
|
2020-02-03 10:24:25 +01:00
|
|
|
switchCase,
|
|
|
|
defaultCase,
|
|
|
|
joinKey,
|
|
|
|
$,
|
|
|
|
isNothing,
|
|
|
|
isSomething,
|
|
|
|
} from "../common"
|
2019-07-15 08:12:52 +02:00
|
|
|
import {
|
2020-02-03 10:24:25 +01:00
|
|
|
isIndex,
|
|
|
|
isRoot,
|
|
|
|
isSingleRecord,
|
|
|
|
isCollectionRecord,
|
|
|
|
isRecord,
|
|
|
|
isaggregateGroup,
|
2019-07-15 08:12:52 +02:00
|
|
|
getFlattenedHierarchy,
|
2020-02-03 10:24:25 +01:00
|
|
|
} from "./hierarchy"
|
|
|
|
import { all } from "../types"
|
|
|
|
import { BadRequestError } from "../common/errors"
|
2019-07-15 08:12:52 +02:00
|
|
|
|
|
|
|
export const createNodeErrors = {
|
2020-02-03 10:24:25 +01:00
|
|
|
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)
|
|
|
|
|
2020-04-02 07:55:11 +02:00
|
|
|
const nodeNameMaker = node => () =>
|
2020-04-14 10:49:10 +02:00
|
|
|
isRoot(node)
|
2020-04-02 07:55:11 +02:00
|
|
|
? "/"
|
|
|
|
: joinKey(node.parent().nodeName(), node.name)
|
|
|
|
|
2020-02-03 10:24:25 +01:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2020-02-03 10:24:25 +01:00
|
|
|
if (isaggregateGroup(node) && isSomething(parent) && !isIndex(parent)) {
|
|
|
|
throw new BadRequestError(createNodeErrors.aggregateParentMustBeAnIndex)
|
2019-07-15 08:12:52 +02:00
|
|
|
}
|
|
|
|
|
2020-02-03 10:24:25 +01:00
|
|
|
if (isNothing(parent) && !isRoot(node)) {
|
|
|
|
throw new BadRequestError(createNodeErrors.allNonRootNodesMustHaveParent)
|
|
|
|
}
|
2019-07-15 08:12:52 +02:00
|
|
|
|
2020-02-03 10:24:25 +01:00
|
|
|
return node
|
|
|
|
}
|
2019-07-15 08:12:52 +02:00
|
|
|
|
2020-02-03 10:24:25 +01:00
|
|
|
const construct = parent => node => {
|
|
|
|
node.nodeKey = nodeKeyMaker(node)
|
2020-04-02 07:55:11 +02:00
|
|
|
node.nodeName = nodeNameMaker(node)
|
2020-02-03 10:24:25 +01:00
|
|
|
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)) {
|
2020-02-03 10:24:25 +01:00
|
|
|
node.collectionNodeKey = () =>
|
|
|
|
joinKey(parent.nodeKey(), node.collectionName)
|
|
|
|
node.collectionPathRegx = () =>
|
|
|
|
joinKey(parent.pathRegx(), node.collectionName)
|
2019-07-15 08:12:52 +02:00
|
|
|
}
|
2020-02-03 10:24:25 +01:00
|
|
|
return node
|
|
|
|
}
|
2019-07-15 08:12:52 +02:00
|
|
|
|
2020-02-03 10:24:25 +01:00
|
|
|
const addToParent = obj => {
|
|
|
|
const parent = obj.parent()
|
2019-07-15 08:12:52 +02:00
|
|
|
if (isSomething(parent)) {
|
2020-02-03 10:24:25 +01:00
|
|
|
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,
|
2020-02-03 10:24:25 +01:00
|
|
|
i => i.name === `${parent.name}_index`
|
|
|
|
)
|
2019-07-15 08:12:52 +02:00
|
|
|
if (defaultIndex) {
|
2020-02-03 10:24:25 +01:00
|
|
|
defaultIndex.allowedRecordNodeIds.push(obj.nodeId)
|
2019-07-15 08:12:52 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-02-03 10:24:25 +01:00
|
|
|
return obj
|
|
|
|
}
|
2019-07-15 08:12:52 +02:00
|
|
|
|
2020-02-03 10:24:25 +01:00
|
|
|
export const constructNode = (parent, obj) =>
|
|
|
|
$(obj, [construct(parent), validate(parent), addToParent])
|
2019-07-15 08:12:52 +02:00
|
|
|
|
2020-02-03 10:24:25 +01:00
|
|
|
const getNodeId = parentNode => {
|
2019-07-15 08:12:52 +02:00
|
|
|
// this case is handled better elsewhere
|
2020-02-03 10:24:25 +01:00
|
|
|
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
|
|
|
|
2020-02-03 10:24:25 +01:00
|
|
|
return $(root, [getFlattenedHierarchy, map(n => n.nodeId), max]) + 1
|
|
|
|
}
|
2019-07-15 08:12:52 +02:00
|
|
|
|
|
|
|
export const constructHierarchy = (node, parent) => {
|
2020-02-03 10:24:25 +01:00
|
|
|
construct(parent)(node)
|
2019-07-15 08:12:52 +02:00
|
|
|
if (node.indexes) {
|
2020-02-03 10:24:25 +01:00
|
|
|
each(node.indexes, child => constructHierarchy(child, node))
|
2019-07-15 08:12:52 +02:00
|
|
|
}
|
|
|
|
if (node.aggregateGroups) {
|
2020-02-03 10:24:25 +01:00
|
|
|
each(node.aggregateGroups, child => constructHierarchy(child, node))
|
2019-07-15 08:12:52 +02:00
|
|
|
}
|
|
|
|
if (node.children && node.children.length > 0) {
|
2020-02-03 10:24:25 +01:00
|
|
|
each(node.children, child => constructHierarchy(child, node))
|
2019-07-15 08:12:52 +02:00
|
|
|
}
|
|
|
|
if (node.fields) {
|
2020-02-03 10:24:25 +01:00
|
|
|
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
|
2020-02-03 10:24:25 +01:00
|
|
|
delete f.typeOptions[key]
|
2019-07-15 08:12:52 +02:00
|
|
|
} else {
|
2020-02-03 10:24:25 +01:00
|
|
|
f.typeOptions[key] = def.parse(val)
|
2019-07-15 08:12:52 +02:00
|
|
|
}
|
2020-02-03 10:24:25 +01:00
|
|
|
})
|
|
|
|
)
|
2019-07-15 08:12:52 +02:00
|
|
|
}
|
2020-02-03 10:24:25 +01:00
|
|
|
return node
|
|
|
|
}
|
2019-07-15 08:12:52 +02:00
|
|
|
|
2020-02-03 10:24:25 +01:00
|
|
|
export const getNewRootLevel = () =>
|
|
|
|
construct()({
|
|
|
|
name: "root",
|
|
|
|
type: "root",
|
|
|
|
children: [],
|
|
|
|
pathMaps: [],
|
|
|
|
indexes: [],
|
|
|
|
nodeId: 0,
|
|
|
|
})
|
2019-07-15 08:12:52 +02:00
|
|
|
|
2020-04-14 10:49:10 +02:00
|
|
|
const _getNewModelTemplate = (parent, name, createDefaultIndex, isSingle) => {
|
2020-03-25 11:45:11 +01:00
|
|
|
const nodeId = getNodeId(parent)
|
2019-07-15 08:12:52 +02:00
|
|
|
const node = constructNode(parent, {
|
|
|
|
name,
|
2020-02-03 10:24:25 +01:00
|
|
|
type: "record",
|
2019-07-15 08:12:52 +02:00
|
|
|
fields: [],
|
|
|
|
children: [],
|
|
|
|
validationRules: [],
|
2020-03-25 11:45:11 +01:00
|
|
|
nodeId: nodeId,
|
2019-07-15 08:12:52 +02:00
|
|
|
indexes: [],
|
2019-12-22 08:12:21 +01:00
|
|
|
estimatedRecordCount: isRecord(parent) ? 500 : 1000000,
|
2020-03-25 11:45:11 +01:00
|
|
|
collectionName: (nodeId || "").toString(),
|
2019-07-15 08:12:52 +02:00
|
|
|
isSingle,
|
2020-02-03 10:24:25 +01:00
|
|
|
})
|
2019-07-15 08:12:52 +02:00
|
|
|
|
|
|
|
if (createDefaultIndex) {
|
2020-02-03 10:24:25 +01:00
|
|
|
const defaultIndex = getNewIndexTemplate(parent)
|
|
|
|
defaultIndex.name = `${name}_index`
|
|
|
|
defaultIndex.allowedRecordNodeIds.push(node.nodeId)
|
2019-07-15 08:12:52 +02:00
|
|
|
}
|
|
|
|
|
2020-02-03 10:24:25 +01:00
|
|
|
return node
|
|
|
|
}
|
|
|
|
|
2020-04-14 10:49:10 +02:00
|
|
|
export const getNewModelTemplate = (
|
2020-02-03 10:24:25 +01:00
|
|
|
parent,
|
|
|
|
name = "",
|
|
|
|
createDefaultIndex = true
|
2020-04-14 10:49:10 +02:00
|
|
|
) => _getNewModelTemplate(parent, name, createDefaultIndex, false)
|
2020-02-03 10:24:25 +01:00
|
|
|
|
|
|
|
export const getNewSingleRecordTemplate = parent =>
|
2020-04-14 10:49:10 +02:00
|
|
|
_getNewModelTemplate(parent, "", false, true)
|
2020-02-03 10:24:25 +01:00
|
|
|
|
|
|
|
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 = {
|
2020-02-03 10:24:25 +01:00
|
|
|
name: "",
|
|
|
|
aggregatedValue: "",
|
|
|
|
}
|
|
|
|
set.aggregates.push(aggregatedValue)
|
|
|
|
return aggregatedValue
|
|
|
|
}
|
2019-07-15 08:12:52 +02:00
|
|
|
|
|
|
|
export default {
|
|
|
|
getNewRootLevel,
|
2020-04-14 10:49:10 +02:00
|
|
|
getNewModelTemplate,
|
2019-07-15 08:12:52 +02:00
|
|
|
getNewIndexTemplate,
|
|
|
|
createNodeErrors,
|
|
|
|
constructHierarchy,
|
|
|
|
getNewAggregateGroupTemplate,
|
|
|
|
getNewAggregateTemplate,
|
2020-02-03 10:24:25 +01:00
|
|
|
}
|