Merge branch 'new-backend' of github.com:Budibase/budibase into new-backend
This commit is contained in:
commit
19c0bbc865
|
@ -13,6 +13,8 @@ import {
|
|||
constructHierarchy,
|
||||
templateApi,
|
||||
isIndex,
|
||||
canDeleteIndex,
|
||||
canDeleteRecord
|
||||
} from "../../common/core"
|
||||
|
||||
export const getBackendUiStore = () => {
|
||||
|
@ -183,7 +185,7 @@ export const saveCurrentNode = store => () => {
|
|||
const defaultIndex = templateApi(state.hierarchy).getNewIndexTemplate(
|
||||
cloned.parent()
|
||||
)
|
||||
defaultIndex.name = `all_${cloned.collectionName}`
|
||||
defaultIndex.name = `all_${cloned.name}s`
|
||||
defaultIndex.allowedRecordNodeIds = [cloned.nodeId]
|
||||
}
|
||||
|
||||
|
@ -202,14 +204,27 @@ export const deleteCurrentNode = store => () => {
|
|||
? state.hierarchy.children.find(node => node !== state.currentNode)
|
||||
: nodeToDelete.parent()
|
||||
|
||||
const recordOrIndexKey = hierarchyFunctions.isRecord(nodeToDelete) ? "children" : "indexes";
|
||||
const isRecord = hierarchyFunctions.isRecord(nodeToDelete)
|
||||
|
||||
const check = isRecord
|
||||
? canDeleteRecord(nodeToDelete)
|
||||
: canDeleteIndex(nodeToDelete)
|
||||
|
||||
if (!check.canDelete) {
|
||||
state.errors = check.errors.map(e => ({ error: e }))
|
||||
return state
|
||||
}
|
||||
|
||||
const recordOrIndexKey = isRecord ? "children" : "indexes"
|
||||
|
||||
// remove the selected record or index
|
||||
nodeToDelete.parent()[recordOrIndexKey] = remove(
|
||||
nodeToDelete.parent()[recordOrIndexKey],
|
||||
node => node.nodeId === nodeToDelete.nodeId
|
||||
const newCollection = remove(
|
||||
node => node.nodeId === nodeToDelete.nodeId,
|
||||
nodeToDelete.parent()[recordOrIndexKey]
|
||||
)
|
||||
|
||||
nodeToDelete.parent()[recordOrIndexKey] = newCollection
|
||||
|
||||
state.errors = []
|
||||
saveBackend(state)
|
||||
return state
|
||||
|
|
|
@ -5,25 +5,14 @@
|
|||
</script>
|
||||
|
||||
{#if hasErrors}
|
||||
<div class="error-container">
|
||||
<div uk-alert class="uk-alert-danger">
|
||||
{#each errors as error}
|
||||
<div class="error-row">
|
||||
<div>
|
||||
{error.field ? `${error.field}: ` : ''}{error.error}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.error-container {
|
||||
padding: 10px;
|
||||
border-style: solid;
|
||||
border-color: var(--deletion100);
|
||||
border-radius: var(--borderradiusall);
|
||||
background: var(--deletion75);
|
||||
}
|
||||
|
||||
.error-row {
|
||||
padding: 5px 0px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -9,6 +9,8 @@ import { find, filter, keyBy, flatten, map } from "lodash/fp"
|
|||
import { generateSchema } from "../../../core/src/indexing/indexSchemaCreator"
|
||||
import { generate } from "shortid"
|
||||
|
||||
export { canDeleteIndex } from "../../../core/src/templateApi/canDeleteIndex"
|
||||
export { canDeleteRecord } from "../../../core/src/templateApi/canDeleteRecord"
|
||||
export { userWithFullAccess } from "../../../core/src/index"
|
||||
|
||||
export const pipe = common.$
|
||||
|
|
|
@ -31,9 +31,6 @@
|
|||
</ActionButton>
|
||||
</div>
|
||||
|
||||
{#if $store.errors && $store.errors.length > 0}
|
||||
<ErrorsBox errors={$store.errors} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
import { store } from "../builderStore"
|
||||
import { filter, some, map, compose } from "lodash/fp"
|
||||
import { hierarchy as hierarchyFunctions, common } from "../../../core/src"
|
||||
|
||||
import ErrorsBox from "../common/ErrorsBox.svelte"
|
||||
|
||||
const SNIPPET_EDITORS = {
|
||||
MAP: "Map",
|
||||
FILTER: "Filter",
|
||||
|
@ -49,6 +50,9 @@
|
|||
</heading>
|
||||
<form class="uk-form-stacked root">
|
||||
<h4 class="budibase__label--big">Settings</h4>
|
||||
{#if $store.errors && $store.errors.length > 0}
|
||||
<ErrorsBox errors={$store.errors} />
|
||||
{/if}
|
||||
<div class="uk-grid-small" uk-grid>
|
||||
<div class="uk-width-1-2@s">
|
||||
<Textbox bind:text={index.name} label="Name" />
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
const response = await api.createUser(
|
||||
password,
|
||||
{
|
||||
username,
|
||||
name:username,
|
||||
accessLevels,
|
||||
enabled: true,
|
||||
temporaryAccessId: ""
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
import { common, hierarchy } from "../../../core/src"
|
||||
import { templateApi, pipe, validate } from "../common/core"
|
||||
import ActionsHeader from "./ActionsHeader.svelte"
|
||||
import ErrorsBox from "../common/ErrorsBox.svelte"
|
||||
|
||||
let record
|
||||
let getIndexAllowedRecords
|
||||
|
@ -99,14 +100,15 @@
|
|||
</heading>
|
||||
{#if !editingField}
|
||||
<h4 class="budibase__label--big">Settings</h4>
|
||||
|
||||
{#if $store.errors && $store.errors.length > 0}
|
||||
<ErrorsBox errors={$store.errors} />
|
||||
{/if}
|
||||
|
||||
<form class="uk-form-stacked">
|
||||
|
||||
<Textbox label="Name" bind:text={record.name} on:change={nameChanged} />
|
||||
|
||||
<div class="horizontal-stack">
|
||||
{#if !record.isSingle}
|
||||
<Textbox label="Collection Name" bind:text={record.collectionName} />
|
||||
{/if}
|
||||
<Textbox label="Name" bind:text={record.name} on:change={nameChanged} />
|
||||
<div>
|
||||
<label class="uk-form-label">Parent</label>
|
||||
<div class="uk-form-controls">
|
||||
|
|
|
@ -59,6 +59,7 @@
|
|||
font-size: 0.8rem;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
background: rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
.active {
|
||||
|
|
|
@ -65,6 +65,7 @@
|
|||
font-size: 0.8rem;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
background: rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
.active {
|
||||
|
|
|
@ -75,6 +75,7 @@
|
|||
font-weight: 400;
|
||||
text-transform: uppercase;
|
||||
color: var(--secondary60);
|
||||
background: rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
.switcher > .selected {
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
font-size: 0.8rem;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
background: rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
.active {
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
reduce,
|
||||
find,
|
||||
} from "lodash/fp"
|
||||
import { compileExpression, compileCode } from "../common/compileCode"
|
||||
import { compileCode } from "../common/compileCode"
|
||||
import { $ } from "../common"
|
||||
import { _executeAction } from "./execute"
|
||||
import { BadRequestError, NotFoundError } from "../common/errors"
|
||||
|
@ -49,7 +49,7 @@ const subscribeTriggers = (
|
|||
|
||||
const shouldRunTrigger = (trigger, eventContext) => {
|
||||
if (!trigger.condition) return true
|
||||
const shouldRun = compileExpression(trigger.condition)
|
||||
const shouldRun = compileCode(trigger.condition)
|
||||
return shouldRun({ context: eventContext })
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,25 @@
|
|||
import {
|
||||
compileExpression as cExp,
|
||||
compileCode as cCode,
|
||||
} from "@nx-js/compiler-util"
|
||||
import { includes } from "lodash/fp"
|
||||
|
||||
|
||||
export const compileCode = code => {
|
||||
let func
|
||||
let safeCode
|
||||
|
||||
if (includes("return ")(code)) {
|
||||
safeCode = code
|
||||
} else {
|
||||
let trimmed = code.trim()
|
||||
trimmed = trimmed.endsWith(";")
|
||||
? trimmed.substring(0, trimmed.length - 1)
|
||||
: trimmed
|
||||
safeCode = `return (${trimmed})`
|
||||
}
|
||||
|
||||
try {
|
||||
func = cCode(code)
|
||||
func = cCode(safeCode)
|
||||
} catch (e) {
|
||||
e.message = `Error compiling code : ${code} : ${e.message}`
|
||||
throw e
|
||||
|
@ -15,16 +27,3 @@ export const compileCode = code => {
|
|||
|
||||
return func
|
||||
}
|
||||
|
||||
export const compileExpression = code => {
|
||||
let func
|
||||
|
||||
try {
|
||||
func = cExp(code)
|
||||
} catch (e) {
|
||||
e.message = `Error compiling expression : ${code} : ${e.message}`
|
||||
throw e
|
||||
}
|
||||
|
||||
return func
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { has, isNumber, isUndefined } from "lodash/fp"
|
||||
import { compileExpression, compileCode } from "@nx-js/compiler-util"
|
||||
import { compileCode } from "../common/compileCode"
|
||||
import { safeKey, apiWrapper, events, isNonEmptyString } from "../common"
|
||||
import { iterateIndex } from "../indexing/read"
|
||||
import {
|
||||
|
@ -147,7 +147,7 @@ const applyItemToAggregateResult = (indexNode, result, item) => {
|
|||
const thisGroupResult = result[aggGroup.name]
|
||||
|
||||
if (isNonEmptyString(aggGroup.condition)) {
|
||||
if (!compileExpression(aggGroup.condition)({ record: item })) {
|
||||
if (!compileCode(aggGroup.condition)({ record: item })) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { compileExpression, compileCode } from "@nx-js/compiler-util"
|
||||
import { isUndefined, keys, cloneDeep, isFunction } from "lodash/fp"
|
||||
import { compileCode } from "../common/compileCode"
|
||||
import { isUndefined, keys, cloneDeep, isFunction, includes } from "lodash/fp"
|
||||
import { defineError } from "../common"
|
||||
|
||||
export const filterEval = "FILTER_EVALUATE"
|
||||
|
@ -16,7 +16,7 @@ const getEvaluateResult = () => ({
|
|||
result: null,
|
||||
})
|
||||
|
||||
export const compileFilter = index => compileExpression(index.filter)
|
||||
export const compileFilter = index => compileCode(index.filter)
|
||||
|
||||
export const compileMap = index => compileCode(index.map)
|
||||
|
||||
|
@ -46,6 +46,9 @@ export const mapRecord = (record, index) => {
|
|||
if (isFunction(mapped[key])) {
|
||||
delete mapped[key]
|
||||
}
|
||||
if (key === "IsNew") {
|
||||
delete mapped.IsNew
|
||||
}
|
||||
}
|
||||
|
||||
mapped.key = record.key
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { compileCode } from "@nx-js/compiler-util"
|
||||
import { compileCode } from "../common/compileCode"
|
||||
import { filter, includes, map, last } from "lodash/fp"
|
||||
import {
|
||||
getActualKeyOfParent,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { map, reduce, filter, isEmpty, flatten, each } from "lodash/fp"
|
||||
import { compileExpression } from "@nx-js/compiler-util"
|
||||
import { compileCode } from "../common/compileCode"
|
||||
import _ from "lodash"
|
||||
import { getExactNodeForKey } from "../templateApi/hierarchy"
|
||||
import { validateFieldParse, validateTypeConstraints } from "../types"
|
||||
|
@ -35,7 +35,7 @@ const validateAllTypeConstraints = async (record, recordNode, context) => {
|
|||
|
||||
const runRecordValidationRules = (record, recordNode) => {
|
||||
const runValidationRule = rule => {
|
||||
const isValid = compileExpression(rule.expressionWhenValid)
|
||||
const isValid = compileCode(rule.expressionWhenValid)
|
||||
const expressionContext = { record, _ }
|
||||
return isValid(expressionContext)
|
||||
? { valid: true }
|
||||
|
|
|
@ -23,7 +23,7 @@ export const canDeleteIndex = indexNode => {
|
|||
}
|
||||
return obj
|
||||
},[]),
|
||||
map(f => `field ${f.name} on record ${f.record.name} uses this index as a reference`)
|
||||
map(f => `field "${f.name}" on record "${f.record.name}" uses this index as a reference`)
|
||||
])
|
||||
|
||||
const lookupIndexes = $(flatHierarchy,[
|
||||
|
@ -37,7 +37,7 @@ export const canDeleteIndex = indexNode => {
|
|||
}
|
||||
return obj
|
||||
},[]),
|
||||
map(f => `field ${f.name} on record ${f.record.name} uses this index as a lookup`)
|
||||
map(f => `field "${f.name}" on record "${f.record.name}" uses this index as a lookup`)
|
||||
])
|
||||
|
||||
const errors = [
|
||||
|
|
|
@ -20,15 +20,14 @@ export const canDeleteRecord = recordNode => {
|
|||
])
|
||||
|
||||
const belongsToAncestor = i =>
|
||||
ancestors.includes(i.parent())
|
||||
|
||||
ancestors.includes(i.parent())
|
||||
|
||||
const errorsForNode = node => {
|
||||
const errorsThisNode = $(flatHierarchy, [
|
||||
filter(i => isAncestorIndex(i)
|
||||
&& belongsToAncestor(i)
|
||||
&& includes(node.nodeId)(i.allowedRecordNodeIds)),
|
||||
map(i => `index ${i.name} indexes this record. Please remove the record from allowedRecordIds, or delete the index`)
|
||||
map(i => `index "${i.name}" indexes this record. Please remove the record from the index, or delete the index`)
|
||||
])
|
||||
|
||||
for (let child of node.children) {
|
||||
|
@ -40,5 +39,7 @@ export const canDeleteRecord = recordNode => {
|
|||
return errorsThisNode
|
||||
}
|
||||
|
||||
return errorsForNode(recordNode)
|
||||
const errors = errorsForNode(recordNode)
|
||||
|
||||
return { errors, canDelete: errors.length === 0 }
|
||||
}
|
|
@ -160,16 +160,17 @@ export const getNewRootLevel = () =>
|
|||
})
|
||||
|
||||
const _getNewRecordTemplate = (parent, name, createDefaultIndex, isSingle) => {
|
||||
const nodeId = getNodeId(parent)
|
||||
const node = constructNode(parent, {
|
||||
name,
|
||||
type: "record",
|
||||
fields: [],
|
||||
children: [],
|
||||
validationRules: [],
|
||||
nodeId: getNodeId(parent),
|
||||
nodeId: nodeId,
|
||||
indexes: [],
|
||||
estimatedRecordCount: isRecord(parent) ? 500 : 1000000,
|
||||
collectionName: "",
|
||||
collectionName: (nodeId || "").toString(),
|
||||
isSingle,
|
||||
})
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
isEmpty,
|
||||
has,
|
||||
} from "lodash/fp"
|
||||
import { compileExpression, compileCode } from "@nx-js/compiler-util"
|
||||
import { compileCode } from "../common/compileCode"
|
||||
import {
|
||||
$,
|
||||
isSomething,
|
||||
|
@ -73,7 +73,7 @@ const aggregateGroupRules = [
|
|||
"condition does not compile",
|
||||
a =>
|
||||
isEmpty(a.condition) ||
|
||||
executesWithoutException(() => compileExpression(a.condition))
|
||||
executesWithoutException(() => compileCode(a.condition))
|
||||
),
|
||||
]
|
||||
|
||||
|
@ -196,7 +196,7 @@ const triggerRules = actions => [
|
|||
t => {
|
||||
if (!t.condition) return true
|
||||
try {
|
||||
compileExpression(t.condition)
|
||||
compileCode(t.condition)
|
||||
return true
|
||||
} catch (_) {
|
||||
return false
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { flatten, map, isEmpty } from "lodash/fp"
|
||||
import { compileCode } from "@nx-js/compiler-util"
|
||||
import { compileCode } from "../common/compileCode"
|
||||
import { isNonEmptyString, executesWithoutException, $ } from "../common"
|
||||
import { applyRuleSet, makerule } from "../common/validationCommon"
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import {
|
|||
setupApphierarchy,
|
||||
basicAppHierarchyCreator_WithFields,
|
||||
stubEventHandler,
|
||||
basicAppHierarchyCreator_WithFields_AndIndexes,
|
||||
} from "./specHelpers"
|
||||
import { canDeleteIndex } from "../src/templateApi/canDeleteIndex"
|
||||
import { canDeleteRecord } from "../src/templateApi/canDeleteRecord"
|
||||
|
@ -49,15 +50,37 @@ describe("canDeleteIndex", () => {
|
|||
|
||||
|
||||
describe("canDeleteRecord", () => {
|
||||
it("should return no errors when deletion is valid", () => {
|
||||
it("should return no errors when deletion is valid", async () => {
|
||||
const { appHierarchy } = await setupApphierarchy(
|
||||
basicAppHierarchyCreator_WithFields
|
||||
)
|
||||
|
||||
appHierarchy.root.
|
||||
const result = canDeleteIndex(appHierarchy.customerRecord)
|
||||
appHierarchy.root.indexes = appHierarchy.root.indexes.filter(i => !i.allowedRecordNodeIds.includes(appHierarchy.customerRecord.nodeId))
|
||||
const result = canDeleteRecord(appHierarchy.customerRecord)
|
||||
|
||||
expect(result.canDelete).toBe(true)
|
||||
expect(result.errors).toEqual([])
|
||||
})
|
||||
|
||||
it("should return errors when record is referenced by hierarchal index", async () => {
|
||||
const { appHierarchy } = await setupApphierarchy(
|
||||
basicAppHierarchyCreator_WithFields
|
||||
)
|
||||
|
||||
const result = canDeleteRecord(appHierarchy.customerRecord)
|
||||
|
||||
expect(result.canDelete).toBe(false)
|
||||
expect(result.errors.some(e => e.includes("customer_index"))).toBe(true)
|
||||
})
|
||||
|
||||
it("should return errors when record has a child which cannot be deleted", async () => {
|
||||
const { appHierarchy } = await setupApphierarchy(
|
||||
basicAppHierarchyCreator_WithFields_AndIndexes
|
||||
)
|
||||
|
||||
const result = canDeleteRecord(appHierarchy.customerRecord)
|
||||
|
||||
expect(result.canDelete).toBe(false)
|
||||
expect(result.errors.some(e => e.includes("Outstanding Invoices"))).toBe(true)
|
||||
})
|
||||
})
|
|
@ -25,7 +25,7 @@ describe("hierarchy node creation", () => {
|
|||
expect(record.validationRules).toEqual([])
|
||||
expect(record.indexes).toEqual([])
|
||||
expect(record.parent()).toBe(root)
|
||||
expect(record.collectionName).toBe("")
|
||||
expect(record.collectionName).toBe(record.nodeId.toString())
|
||||
expect(record.estimatedRecordCount).toBe(1000000)
|
||||
expect(record.isSingle).toBe(false)
|
||||
|
||||
|
|
|
@ -171,6 +171,7 @@ module.exports = (config, app) => {
|
|||
ctx.request.body.appDefinition,
|
||||
ctx.request.body.accessLevels
|
||||
)
|
||||
ctx.master.deleteLatestPackageFromCache(ctx.params.appname)
|
||||
ctx.response.status = StatusCodes.OK
|
||||
})
|
||||
.post("/_builder/api/:appname/pages/:pageName", async ctx => {
|
||||
|
|
|
@ -5,12 +5,16 @@ const { getRuntimePackageDirectory } = require("../utilities/runtimePackages")
|
|||
const injectPlugins = require("./injectedPlugins")
|
||||
const { cwd } = require("process")
|
||||
|
||||
const appDefinitionPath = appPath => join(appPath, "appDefinition.json")
|
||||
const pluginsPath = appPath => join(appPath, "plugins.js")
|
||||
const accessLevelsPath = appPath => join(appPath, "access_levels.json")
|
||||
|
||||
const createAppPackage = (context, appPath) => {
|
||||
const appDefModule = require(join(appPath, "appDefinition.json"))
|
||||
const appDefModule = require(appDefinitionPath(appPath))
|
||||
|
||||
const pluginsModule = require(join(appPath, "plugins.js"))
|
||||
const pluginsModule = require(pluginsPath(appPath))
|
||||
|
||||
const accessLevels = require(join(appPath, "access_levels.json"))
|
||||
const accessLevels = require(accessLevelsPath(appPath))
|
||||
|
||||
return {
|
||||
appDefinition: appDefModule,
|
||||
|
@ -87,3 +91,11 @@ module.exports.applictionVersionPackage = async (
|
|||
await injectPlugins(pkg, context.master, appname, instanceKey)
|
||||
return pkg
|
||||
}
|
||||
|
||||
module.exports.deleteCachedPackage = (context, appname, versionId) => {
|
||||
const appPath = applictionVersionPath(context, appname, versionId)
|
||||
|
||||
delete require.cache[resolve(appDefinitionPath(appPath))]
|
||||
delete require.cache[resolve(pluginsPath(appPath))]
|
||||
delete require.cache[resolve(accessLevelsPath(appPath))]
|
||||
}
|
||||
|
|
|
@ -11,8 +11,9 @@ const {
|
|||
masterAppPackage,
|
||||
applictionVersionPackage,
|
||||
applictionVersionPublicPaths,
|
||||
deleteCachedPackage,
|
||||
} = require("../utilities/createAppPackage")
|
||||
const { determineVersionId } = require("./runtimePackages")
|
||||
const { determineVersionId, LATEST_VERSIONID } = require("./runtimePackages")
|
||||
|
||||
const isMaster = appname => appname === "_master"
|
||||
|
||||
|
@ -345,6 +346,10 @@ module.exports = async context => {
|
|||
await bb.recordApi.save(userInMaster)
|
||||
}
|
||||
|
||||
const deleteLatestPackageFromCache = (appname) => {
|
||||
deleteCachedPackage(context, appname, LATEST_VERSIONID)
|
||||
}
|
||||
|
||||
const listApplications = () => values(applications)
|
||||
|
||||
return {
|
||||
|
@ -364,5 +369,6 @@ module.exports = async context => {
|
|||
getFullAccessApiForInstanceId,
|
||||
getFullAccessApiForMaster,
|
||||
getApplicationWithInstances,
|
||||
deleteLatestPackageFromCache,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue