Merge branch 'new-backend' of github.com:Budibase/budibase into new-backend

This commit is contained in:
Martin McKeaveney 2020-03-25 16:59:36 +00:00
commit 19c0bbc865
27 changed files with 135 additions and 76 deletions

View File

@ -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

View File

@ -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>

View File

@ -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.$

View File

@ -31,9 +31,6 @@
</ActionButton>
</div>
{#if $store.errors && $store.errors.length > 0}
<ErrorsBox errors={$store.errors} />
{/if}
</div>
<style>

View File

@ -6,6 +6,7 @@
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",
@ -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" />

View File

@ -20,7 +20,7 @@
const response = await api.createUser(
password,
{
username,
name:username,
accessLevels,
enabled: true,
temporaryAccessId: ""

View File

@ -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">

View File

@ -59,6 +59,7 @@
font-size: 0.8rem;
outline: none;
cursor: pointer;
background: rgba(0,0,0,0);
}
.active {

View File

@ -65,6 +65,7 @@
font-size: 0.8rem;
outline: none;
cursor: pointer;
background: rgba(0,0,0,0);
}
.active {

View File

@ -75,6 +75,7 @@
font-weight: 400;
text-transform: uppercase;
color: var(--secondary60);
background: rgba(0,0,0,0);
}
.switcher > .selected {

View File

@ -70,6 +70,7 @@
font-size: 0.8rem;
outline: none;
cursor: pointer;
background: rgba(0,0,0,0);
}
.active {

View File

@ -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 })
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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,

View File

@ -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 }

View File

@ -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 = [

View File

@ -22,13 +22,12 @@ export const canDeleteRecord = recordNode => {
const belongsToAncestor = i =>
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 }
}

View File

@ -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,
})

View File

@ -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

View File

@ -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"

View File

@ -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)
})
})

View File

@ -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)

View File

@ -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 => {

View File

@ -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))]
}

View File

@ -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,
}
}