Merge branch 'develop' into cypress-testing
This commit is contained in:
commit
fe2a496305
29 changed files with 983 additions and 192 deletions
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "1.0.151-alpha.2",
|
"version": "1.0.155-alpha.0",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/backend-core",
|
"name": "@budibase/backend-core",
|
||||||
"version": "1.0.151-alpha.2",
|
"version": "1.0.155-alpha.0",
|
||||||
"description": "Budibase backend core libraries used in server and worker",
|
"description": "Budibase backend core libraries used in server and worker",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
"@techpass/passport-openidconnect": "^0.3.0",
|
"@techpass/passport-openidconnect": "^0.3.0",
|
||||||
"aws-sdk": "^2.901.0",
|
"aws-sdk": "^2.901.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"cls-hooked": "^4.2.2",
|
"emitter-listener": "^1.1.2",
|
||||||
"ioredis": "^4.27.1",
|
"ioredis": "^4.27.1",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"koa-passport": "^4.1.4",
|
"koa-passport": "^4.1.4",
|
||||||
|
|
650
packages/backend-core/src/clshooked/index.js
Normal file
650
packages/backend-core/src/clshooked/index.js
Normal file
|
@ -0,0 +1,650 @@
|
||||||
|
const util = require("util")
|
||||||
|
const assert = require("assert")
|
||||||
|
const wrapEmitter = require("emitter-listener")
|
||||||
|
const async_hooks = require("async_hooks")
|
||||||
|
|
||||||
|
const CONTEXTS_SYMBOL = "cls@contexts"
|
||||||
|
const ERROR_SYMBOL = "error@context"
|
||||||
|
|
||||||
|
const DEBUG_CLS_HOOKED = process.env.DEBUG_CLS_HOOKED
|
||||||
|
|
||||||
|
let currentUid = -1
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getNamespace: getNamespace,
|
||||||
|
createNamespace: createNamespace,
|
||||||
|
destroyNamespace: destroyNamespace,
|
||||||
|
reset: reset,
|
||||||
|
ERROR_SYMBOL: ERROR_SYMBOL,
|
||||||
|
}
|
||||||
|
|
||||||
|
function Namespace(name) {
|
||||||
|
this.name = name
|
||||||
|
// changed in 2.7: no default context
|
||||||
|
this.active = null
|
||||||
|
this._set = []
|
||||||
|
this.id = null
|
||||||
|
this._contexts = new Map()
|
||||||
|
this._indent = 0
|
||||||
|
this._hook = null
|
||||||
|
}
|
||||||
|
|
||||||
|
Namespace.prototype.set = function set(key, value) {
|
||||||
|
if (!this.active) {
|
||||||
|
throw new Error(
|
||||||
|
"No context available. ns.run() or ns.bind() must be called first."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.active[key] = value
|
||||||
|
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
||||||
|
debug2(
|
||||||
|
indentStr +
|
||||||
|
"CONTEXT-SET KEY:" +
|
||||||
|
key +
|
||||||
|
"=" +
|
||||||
|
value +
|
||||||
|
" in ns:" +
|
||||||
|
this.name +
|
||||||
|
" currentUid:" +
|
||||||
|
currentUid +
|
||||||
|
" active:" +
|
||||||
|
util.inspect(this.active, { showHidden: true, depth: 2, colors: true })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
Namespace.prototype.get = function get(key) {
|
||||||
|
if (!this.active) {
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const asyncHooksCurrentId = async_hooks.currentId()
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}CONTEXT-GETTING KEY NO ACTIVE NS: (${this.name}) ${key}=undefined currentUid:${currentUid} asyncHooksCurrentId:${asyncHooksCurrentId} triggerId:${triggerId} len:${this._set.length}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const asyncHooksCurrentId = async_hooks.executionAsyncId()
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
||||||
|
debug2(
|
||||||
|
indentStr +
|
||||||
|
"CONTEXT-GETTING KEY:" +
|
||||||
|
key +
|
||||||
|
"=" +
|
||||||
|
this.active[key] +
|
||||||
|
" (" +
|
||||||
|
this.name +
|
||||||
|
") currentUid:" +
|
||||||
|
currentUid +
|
||||||
|
" active:" +
|
||||||
|
util.inspect(this.active, { showHidden: true, depth: 2, colors: true })
|
||||||
|
)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}CONTEXT-GETTING KEY: (${this.name}) ${key}=${
|
||||||
|
this.active[key]
|
||||||
|
} currentUid:${currentUid} asyncHooksCurrentId:${asyncHooksCurrentId} triggerId:${triggerId} len:${
|
||||||
|
this._set.length
|
||||||
|
} active:${util.inspect(this.active)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return this.active[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
Namespace.prototype.createContext = function createContext() {
|
||||||
|
// Prototype inherit existing context if created a new child context within existing context.
|
||||||
|
let context = Object.create(this.active ? this.active : Object.prototype)
|
||||||
|
context._ns_name = this.name
|
||||||
|
context.id = currentUid
|
||||||
|
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const asyncHooksCurrentId = async_hooks.executionAsyncId()
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}CONTEXT-CREATED Context: (${
|
||||||
|
this.name
|
||||||
|
}) currentUid:${currentUid} asyncHooksCurrentId:${asyncHooksCurrentId} triggerId:${triggerId} len:${
|
||||||
|
this._set.length
|
||||||
|
} context:${util.inspect(context, {
|
||||||
|
showHidden: true,
|
||||||
|
depth: 2,
|
||||||
|
colors: true,
|
||||||
|
})}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
Namespace.prototype.run = function run(fn) {
|
||||||
|
let context = this.createContext()
|
||||||
|
this.enter(context)
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
const asyncHooksCurrentId = async_hooks.executionAsyncId()
|
||||||
|
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}CONTEXT-RUN BEGIN: (${
|
||||||
|
this.name
|
||||||
|
}) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${
|
||||||
|
this._set.length
|
||||||
|
} context:${util.inspect(context)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn(context)
|
||||||
|
return context
|
||||||
|
} catch (exception) {
|
||||||
|
if (exception) {
|
||||||
|
exception[ERROR_SYMBOL] = context
|
||||||
|
}
|
||||||
|
throw exception
|
||||||
|
} finally {
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
const asyncHooksCurrentId = async_hooks.executionAsyncId()
|
||||||
|
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}CONTEXT-RUN END: (${
|
||||||
|
this.name
|
||||||
|
}) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${
|
||||||
|
this._set.length
|
||||||
|
} ${util.inspect(context)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.exit(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Namespace.prototype.runAndReturn = function runAndReturn(fn) {
|
||||||
|
let value
|
||||||
|
this.run(function (context) {
|
||||||
|
value = fn(context)
|
||||||
|
})
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses global Promise and assumes Promise is cls friendly or wrapped already.
|
||||||
|
* @param {function} fn
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
Namespace.prototype.runPromise = function runPromise(fn) {
|
||||||
|
let context = this.createContext()
|
||||||
|
this.enter(context)
|
||||||
|
|
||||||
|
let promise = fn(context)
|
||||||
|
if (!promise || !promise.then || !promise.catch) {
|
||||||
|
throw new Error("fn must return a promise.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
debug2(
|
||||||
|
"CONTEXT-runPromise BEFORE: (" +
|
||||||
|
this.name +
|
||||||
|
") currentUid:" +
|
||||||
|
currentUid +
|
||||||
|
" len:" +
|
||||||
|
this._set.length +
|
||||||
|
" " +
|
||||||
|
util.inspect(context)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise
|
||||||
|
.then(result => {
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
debug2(
|
||||||
|
"CONTEXT-runPromise AFTER then: (" +
|
||||||
|
this.name +
|
||||||
|
") currentUid:" +
|
||||||
|
currentUid +
|
||||||
|
" len:" +
|
||||||
|
this._set.length +
|
||||||
|
" " +
|
||||||
|
util.inspect(context)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.exit(context)
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
err[ERROR_SYMBOL] = context
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
debug2(
|
||||||
|
"CONTEXT-runPromise AFTER catch: (" +
|
||||||
|
this.name +
|
||||||
|
") currentUid:" +
|
||||||
|
currentUid +
|
||||||
|
" len:" +
|
||||||
|
this._set.length +
|
||||||
|
" " +
|
||||||
|
util.inspect(context)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.exit(context)
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Namespace.prototype.bind = function bindFactory(fn, context) {
|
||||||
|
if (!context) {
|
||||||
|
if (!this.active) {
|
||||||
|
context = this.createContext()
|
||||||
|
} else {
|
||||||
|
context = this.active
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let self = this
|
||||||
|
return function clsBind() {
|
||||||
|
self.enter(context)
|
||||||
|
try {
|
||||||
|
return fn.apply(this, arguments)
|
||||||
|
} catch (exception) {
|
||||||
|
if (exception) {
|
||||||
|
exception[ERROR_SYMBOL] = context
|
||||||
|
}
|
||||||
|
throw exception
|
||||||
|
} finally {
|
||||||
|
self.exit(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Namespace.prototype.enter = function enter(context) {
|
||||||
|
assert.ok(context, "context must be provided for entering")
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const asyncHooksCurrentId = async_hooks.executionAsyncId()
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}CONTEXT-ENTER: (${
|
||||||
|
this.name
|
||||||
|
}) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${
|
||||||
|
this._set.length
|
||||||
|
} ${util.inspect(context)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
this._set.push(this.active)
|
||||||
|
this.active = context
|
||||||
|
}
|
||||||
|
|
||||||
|
Namespace.prototype.exit = function exit(context) {
|
||||||
|
assert.ok(context, "context must be provided for exiting")
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const asyncHooksCurrentId = async_hooks.executionAsyncId()
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}CONTEXT-EXIT: (${
|
||||||
|
this.name
|
||||||
|
}) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${
|
||||||
|
this._set.length
|
||||||
|
} ${util.inspect(context)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast path for most exits that are at the top of the stack
|
||||||
|
if (this.active === context) {
|
||||||
|
assert.ok(this._set.length, "can't remove top context")
|
||||||
|
this.active = this._set.pop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast search in the stack using lastIndexOf
|
||||||
|
let index = this._set.lastIndexOf(context)
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
debug2(
|
||||||
|
"??ERROR?? context exiting but not entered - ignoring: " +
|
||||||
|
util.inspect(context)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
assert.ok(
|
||||||
|
index >= 0,
|
||||||
|
"context not currently entered; can't exit. \n" +
|
||||||
|
util.inspect(this) +
|
||||||
|
"\n" +
|
||||||
|
util.inspect(context)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
assert.ok(index, "can't remove top context")
|
||||||
|
this._set.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Namespace.prototype.bindEmitter = function bindEmitter(emitter) {
|
||||||
|
assert.ok(
|
||||||
|
emitter.on && emitter.addListener && emitter.emit,
|
||||||
|
"can only bind real EEs"
|
||||||
|
)
|
||||||
|
|
||||||
|
let namespace = this
|
||||||
|
let thisSymbol = "context@" + this.name
|
||||||
|
|
||||||
|
// Capture the context active at the time the emitter is bound.
|
||||||
|
function attach(listener) {
|
||||||
|
if (!listener) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!listener[CONTEXTS_SYMBOL]) {
|
||||||
|
listener[CONTEXTS_SYMBOL] = Object.create(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
listener[CONTEXTS_SYMBOL][thisSymbol] = {
|
||||||
|
namespace: namespace,
|
||||||
|
context: namespace.active,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// At emit time, bind the listener within the correct context.
|
||||||
|
function bind(unwrapped) {
|
||||||
|
if (!(unwrapped && unwrapped[CONTEXTS_SYMBOL])) {
|
||||||
|
return unwrapped
|
||||||
|
}
|
||||||
|
|
||||||
|
let wrapped = unwrapped
|
||||||
|
let unwrappedContexts = unwrapped[CONTEXTS_SYMBOL]
|
||||||
|
Object.keys(unwrappedContexts).forEach(function (name) {
|
||||||
|
let thunk = unwrappedContexts[name]
|
||||||
|
wrapped = thunk.namespace.bind(wrapped, thunk.context)
|
||||||
|
})
|
||||||
|
return wrapped
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapEmitter(emitter, attach, bind)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If an error comes out of a namespace, it will have a context attached to it.
|
||||||
|
* This function knows how to find it.
|
||||||
|
*
|
||||||
|
* @param {Error} exception Possibly annotated error.
|
||||||
|
*/
|
||||||
|
Namespace.prototype.fromException = function fromException(exception) {
|
||||||
|
return exception[ERROR_SYMBOL]
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNamespace(name) {
|
||||||
|
return process.namespaces[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNamespace(name) {
|
||||||
|
assert.ok(name, "namespace must be given a name.")
|
||||||
|
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
debug2(`NS-CREATING NAMESPACE (${name})`)
|
||||||
|
}
|
||||||
|
let namespace = new Namespace(name)
|
||||||
|
namespace.id = currentUid
|
||||||
|
|
||||||
|
const hook = async_hooks.createHook({
|
||||||
|
init(asyncId, type, triggerId, resource) {
|
||||||
|
currentUid = async_hooks.executionAsyncId()
|
||||||
|
|
||||||
|
//CHAIN Parent's Context onto child if none exists. This is needed to pass net-events.spec
|
||||||
|
// let initContext = namespace.active;
|
||||||
|
// if(!initContext && triggerId) {
|
||||||
|
// let parentContext = namespace._contexts.get(triggerId);
|
||||||
|
// if (parentContext) {
|
||||||
|
// namespace.active = parentContext;
|
||||||
|
// namespace._contexts.set(currentUid, parentContext);
|
||||||
|
// if (DEBUG_CLS_HOOKED) {
|
||||||
|
// const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent);
|
||||||
|
// debug2(`${indentStr}INIT [${type}] (${name}) WITH PARENT CONTEXT asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, true)} resource:${resource}`);
|
||||||
|
// }
|
||||||
|
// } else if (DEBUG_CLS_HOOKED) {
|
||||||
|
// const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent);
|
||||||
|
// debug2(`${indentStr}INIT [${type}] (${name}) MISSING CONTEXT asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, true)} resource:${resource}`);
|
||||||
|
// }
|
||||||
|
// }else {
|
||||||
|
// namespace._contexts.set(currentUid, namespace.active);
|
||||||
|
// if (DEBUG_CLS_HOOKED) {
|
||||||
|
// const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent);
|
||||||
|
// debug2(`${indentStr}INIT [${type}] (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, true)} resource:${resource}`);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
if (namespace.active) {
|
||||||
|
namespace._contexts.set(asyncId, namespace.active)
|
||||||
|
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const indentStr = " ".repeat(
|
||||||
|
namespace._indent < 0 ? 0 : namespace._indent
|
||||||
|
)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}INIT [${type}] (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
||||||
|
namespace.active,
|
||||||
|
{ showHidden: true, depth: 2, colors: true }
|
||||||
|
)} resource:${resource}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (currentUid === 0) {
|
||||||
|
// CurrentId will be 0 when triggered from C++. Promise events
|
||||||
|
// https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md#triggerid
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
const triggerIdContext = namespace._contexts.get(triggerId)
|
||||||
|
if (triggerIdContext) {
|
||||||
|
namespace._contexts.set(asyncId, triggerIdContext)
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const indentStr = " ".repeat(
|
||||||
|
namespace._indent < 0 ? 0 : namespace._indent
|
||||||
|
)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}INIT USING CONTEXT FROM TRIGGERID [${type}] (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
||||||
|
namespace.active,
|
||||||
|
{ showHidden: true, depth: 2, colors: true }
|
||||||
|
)} resource:${resource}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (DEBUG_CLS_HOOKED) {
|
||||||
|
const indentStr = " ".repeat(
|
||||||
|
namespace._indent < 0 ? 0 : namespace._indent
|
||||||
|
)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}INIT MISSING CONTEXT [${type}] (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
||||||
|
namespace.active,
|
||||||
|
{ showHidden: true, depth: 2, colors: true }
|
||||||
|
)} resource:${resource}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG_CLS_HOOKED && type === "PROMISE") {
|
||||||
|
debug2(util.inspect(resource, { showHidden: true }))
|
||||||
|
const parentId = resource.parentId
|
||||||
|
const indentStr = " ".repeat(
|
||||||
|
namespace._indent < 0 ? 0 : namespace._indent
|
||||||
|
)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}INIT RESOURCE-PROMISE [${type}] (${name}) parentId:${parentId} asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
||||||
|
namespace.active,
|
||||||
|
{ showHidden: true, depth: 2, colors: true }
|
||||||
|
)} resource:${resource}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
before(asyncId) {
|
||||||
|
currentUid = async_hooks.executionAsyncId()
|
||||||
|
let context
|
||||||
|
|
||||||
|
/*
|
||||||
|
if(currentUid === 0){
|
||||||
|
// CurrentId will be 0 when triggered from C++. Promise events
|
||||||
|
// https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md#triggerid
|
||||||
|
//const triggerId = async_hooks.triggerAsyncId();
|
||||||
|
context = namespace._contexts.get(asyncId); // || namespace._contexts.get(triggerId);
|
||||||
|
}else{
|
||||||
|
context = namespace._contexts.get(currentUid);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
//HACK to work with promises until they are fixed in node > 8.1.1
|
||||||
|
context =
|
||||||
|
namespace._contexts.get(asyncId) || namespace._contexts.get(currentUid)
|
||||||
|
|
||||||
|
if (context) {
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
const indentStr = " ".repeat(
|
||||||
|
namespace._indent < 0 ? 0 : namespace._indent
|
||||||
|
)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}BEFORE (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
||||||
|
namespace.active,
|
||||||
|
{ showHidden: true, depth: 2, colors: true }
|
||||||
|
)} context:${util.inspect(context)}`
|
||||||
|
)
|
||||||
|
namespace._indent += 2
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace.enter(context)
|
||||||
|
} else if (DEBUG_CLS_HOOKED) {
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
const indentStr = " ".repeat(
|
||||||
|
namespace._indent < 0 ? 0 : namespace._indent
|
||||||
|
)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}BEFORE MISSING CONTEXT (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
||||||
|
namespace.active,
|
||||||
|
{ showHidden: true, depth: 2, colors: true }
|
||||||
|
)} namespace._contexts:${util.inspect(namespace._contexts, {
|
||||||
|
showHidden: true,
|
||||||
|
depth: 2,
|
||||||
|
colors: true,
|
||||||
|
})}`
|
||||||
|
)
|
||||||
|
namespace._indent += 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
after(asyncId) {
|
||||||
|
currentUid = async_hooks.executionAsyncId()
|
||||||
|
let context // = namespace._contexts.get(currentUid);
|
||||||
|
/*
|
||||||
|
if(currentUid === 0){
|
||||||
|
// CurrentId will be 0 when triggered from C++. Promise events
|
||||||
|
// https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md#triggerid
|
||||||
|
//const triggerId = async_hooks.triggerAsyncId();
|
||||||
|
context = namespace._contexts.get(asyncId); // || namespace._contexts.get(triggerId);
|
||||||
|
}else{
|
||||||
|
context = namespace._contexts.get(currentUid);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
//HACK to work with promises until they are fixed in node > 8.1.1
|
||||||
|
context =
|
||||||
|
namespace._contexts.get(asyncId) || namespace._contexts.get(currentUid)
|
||||||
|
|
||||||
|
if (context) {
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
namespace._indent -= 2
|
||||||
|
const indentStr = " ".repeat(
|
||||||
|
namespace._indent < 0 ? 0 : namespace._indent
|
||||||
|
)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}AFTER (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
||||||
|
namespace.active,
|
||||||
|
{ showHidden: true, depth: 2, colors: true }
|
||||||
|
)} context:${util.inspect(context)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace.exit(context)
|
||||||
|
} else if (DEBUG_CLS_HOOKED) {
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
namespace._indent -= 2
|
||||||
|
const indentStr = " ".repeat(
|
||||||
|
namespace._indent < 0 ? 0 : namespace._indent
|
||||||
|
)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}AFTER MISSING CONTEXT (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
||||||
|
namespace.active,
|
||||||
|
{ showHidden: true, depth: 2, colors: true }
|
||||||
|
)} context:${util.inspect(context)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
destroy(asyncId) {
|
||||||
|
currentUid = async_hooks.executionAsyncId()
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
const triggerId = async_hooks.triggerAsyncId()
|
||||||
|
const indentStr = " ".repeat(
|
||||||
|
namespace._indent < 0 ? 0 : namespace._indent
|
||||||
|
)
|
||||||
|
debug2(
|
||||||
|
`${indentStr}DESTROY (${name}) currentUid:${currentUid} asyncId:${asyncId} triggerId:${triggerId} active:${util.inspect(
|
||||||
|
namespace.active,
|
||||||
|
{ showHidden: true, depth: 2, colors: true }
|
||||||
|
)} context:${util.inspect(namespace._contexts.get(currentUid))}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace._contexts.delete(asyncId)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
hook.enable()
|
||||||
|
namespace._hook = hook
|
||||||
|
|
||||||
|
process.namespaces[name] = namespace
|
||||||
|
return namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
function destroyNamespace(name) {
|
||||||
|
let namespace = getNamespace(name)
|
||||||
|
|
||||||
|
assert.ok(namespace, "can't delete nonexistent namespace! \"" + name + '"')
|
||||||
|
assert.ok(
|
||||||
|
namespace.id,
|
||||||
|
"don't assign to process.namespaces directly! " + util.inspect(namespace)
|
||||||
|
)
|
||||||
|
|
||||||
|
namespace._hook.disable()
|
||||||
|
namespace._contexts = null
|
||||||
|
process.namespaces[name] = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
// must unregister async listeners
|
||||||
|
if (process.namespaces) {
|
||||||
|
Object.keys(process.namespaces).forEach(function (name) {
|
||||||
|
destroyNamespace(name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
process.namespaces = Object.create(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
process.namespaces = process.namespaces || {}
|
||||||
|
|
||||||
|
//const fs = require('fs');
|
||||||
|
function debug2(...args) {
|
||||||
|
if (DEBUG_CLS_HOOKED) {
|
||||||
|
//fs.writeSync(1, `${util.format(...args)}\n`);
|
||||||
|
process._rawDebug(`${util.format(...args)}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*function getFunctionName(fn) {
|
||||||
|
if (!fn) {
|
||||||
|
return fn;
|
||||||
|
}
|
||||||
|
if (typeof fn === 'function') {
|
||||||
|
if (fn.name) {
|
||||||
|
return fn.name;
|
||||||
|
}
|
||||||
|
return (fn.toString().trim().match(/^function\s*([^\s(]+)/) || [])[1];
|
||||||
|
} else if (fn.constructor && fn.constructor.name) {
|
||||||
|
return fn.constructor.name;
|
||||||
|
}
|
||||||
|
}*/
|
|
@ -1,84 +1,47 @@
|
||||||
const cls = require("cls-hooked")
|
const cls = require("../clshooked")
|
||||||
const { newid } = require("../hashing")
|
const { newid } = require("../hashing")
|
||||||
|
|
||||||
const REQUEST_ID_KEY = "requestId"
|
const REQUEST_ID_KEY = "requestId"
|
||||||
|
const MAIN_CTX = cls.createNamespace("main")
|
||||||
|
|
||||||
|
function getContextStorage(namespace) {
|
||||||
|
if (namespace && namespace.active) {
|
||||||
|
let contextData = namespace.active
|
||||||
|
delete contextData.id
|
||||||
|
delete contextData._ns_name
|
||||||
|
return contextData
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
class FunctionContext {
|
class FunctionContext {
|
||||||
static getMiddleware(
|
static run(callback) {
|
||||||
updateCtxFn = null,
|
return MAIN_CTX.runAndReturn(async () => {
|
||||||
destroyFn = null,
|
const namespaceId = newid()
|
||||||
contextName = "session"
|
MAIN_CTX.set(REQUEST_ID_KEY, namespaceId)
|
||||||
) {
|
const namespace = cls.createNamespace(namespaceId)
|
||||||
const namespace = this.createNamespace(contextName)
|
let response = await namespace.runAndReturn(callback)
|
||||||
|
cls.destroyNamespace(namespaceId)
|
||||||
return async function (ctx, next) {
|
return response
|
||||||
await new Promise(
|
})
|
||||||
namespace.bind(function (resolve, reject) {
|
|
||||||
// store a contextual request ID that can be used anywhere (audit logs)
|
|
||||||
namespace.set(REQUEST_ID_KEY, newid())
|
|
||||||
namespace.bindEmitter(ctx.req)
|
|
||||||
namespace.bindEmitter(ctx.res)
|
|
||||||
|
|
||||||
if (updateCtxFn) {
|
|
||||||
updateCtxFn(ctx)
|
|
||||||
}
|
|
||||||
next()
|
|
||||||
.then(resolve)
|
|
||||||
.catch(reject)
|
|
||||||
.finally(() => {
|
|
||||||
if (destroyFn) {
|
|
||||||
return destroyFn(ctx)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static run(callback, contextName = "session") {
|
static setOnContext(key, value) {
|
||||||
const namespace = this.createNamespace(contextName)
|
const namespaceId = MAIN_CTX.get(REQUEST_ID_KEY)
|
||||||
|
const namespace = cls.getNamespace(namespaceId)
|
||||||
return namespace.runAndReturn(callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
static setOnContext(key, value, contextName = "session") {
|
|
||||||
const namespace = this.createNamespace(contextName)
|
|
||||||
namespace.set(key, value)
|
namespace.set(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
static getContextStorage() {
|
|
||||||
if (this._namespace && this._namespace.active) {
|
|
||||||
let contextData = this._namespace.active
|
|
||||||
delete contextData.id
|
|
||||||
delete contextData._ns_name
|
|
||||||
return contextData
|
|
||||||
}
|
|
||||||
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
static getFromContext(key) {
|
static getFromContext(key) {
|
||||||
const context = this.getContextStorage()
|
const namespaceId = MAIN_CTX.get(REQUEST_ID_KEY)
|
||||||
|
const namespace = cls.getNamespace(namespaceId)
|
||||||
|
const context = getContextStorage(namespace)
|
||||||
if (context) {
|
if (context) {
|
||||||
return context[key]
|
return context[key]
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static destroyNamespace(name = "session") {
|
|
||||||
if (this._namespace) {
|
|
||||||
cls.destroyNamespace(name)
|
|
||||||
this._namespace = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static createNamespace(name = "session") {
|
|
||||||
if (!this._namespace) {
|
|
||||||
this._namespace = cls.createNamespace(name)
|
|
||||||
}
|
|
||||||
return this._namespace
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = FunctionContext
|
module.exports = FunctionContext
|
||||||
|
|
|
@ -55,6 +55,15 @@ async function closeAppDBs() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.closeTenancy = async () => {
|
||||||
|
if (env.USE_COUCH) {
|
||||||
|
await closeDB(exports.getGlobalDB())
|
||||||
|
}
|
||||||
|
// clear from context now that database is closed/task is finished
|
||||||
|
cls.setOnContext(ContextKeys.TENANT_ID, null)
|
||||||
|
cls.setOnContext(ContextKeys.GLOBAL_DB, null)
|
||||||
|
}
|
||||||
|
|
||||||
exports.isDefaultTenant = () => {
|
exports.isDefaultTenant = () => {
|
||||||
return exports.getTenantId() === exports.DEFAULT_TENANT_ID
|
return exports.getTenantId() === exports.DEFAULT_TENANT_ID
|
||||||
}
|
}
|
||||||
|
@ -82,12 +91,7 @@ exports.doInTenant = (tenantId, task) => {
|
||||||
} finally {
|
} finally {
|
||||||
const using = cls.getFromContext(ContextKeys.IN_USE)
|
const using = cls.getFromContext(ContextKeys.IN_USE)
|
||||||
if (!using || using <= 1) {
|
if (!using || using <= 1) {
|
||||||
if (env.USE_COUCH) {
|
await exports.closeTenancy()
|
||||||
await closeDB(exports.getGlobalDB())
|
|
||||||
}
|
|
||||||
// clear from context now that database is closed/task is finished
|
|
||||||
cls.setOnContext(ContextKeys.TENANT_ID, null)
|
|
||||||
cls.setOnContext(ContextKeys.GLOBAL_DB, null)
|
|
||||||
} else {
|
} else {
|
||||||
cls.setOnContext(using - 1)
|
cls.setOnContext(using - 1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
const { setTenantId, setGlobalDB, getGlobalDB } = require("../tenancy")
|
const { setTenantId, setGlobalDB, closeTenancy } = require("../tenancy")
|
||||||
const { closeDB } = require("../db")
|
const cls = require("../context/FunctionContext")
|
||||||
const ContextFactory = require("../context/FunctionContext")
|
|
||||||
const { buildMatcherRegex, matches } = require("./matchers")
|
const { buildMatcherRegex, matches } = require("./matchers")
|
||||||
|
|
||||||
module.exports = (
|
module.exports = (
|
||||||
|
@ -11,17 +10,16 @@ module.exports = (
|
||||||
const allowQsOptions = buildMatcherRegex(allowQueryStringPatterns)
|
const allowQsOptions = buildMatcherRegex(allowQueryStringPatterns)
|
||||||
const noTenancyOptions = buildMatcherRegex(noTenancyPatterns)
|
const noTenancyOptions = buildMatcherRegex(noTenancyPatterns)
|
||||||
|
|
||||||
const updateCtxFn = ctx => {
|
return async function (ctx, next) {
|
||||||
const allowNoTenant =
|
return cls.run(async () => {
|
||||||
opts.noTenancyRequired || !!matches(ctx, noTenancyOptions)
|
const allowNoTenant =
|
||||||
const allowQs = !!matches(ctx, allowQsOptions)
|
opts.noTenancyRequired || !!matches(ctx, noTenancyOptions)
|
||||||
const tenantId = setTenantId(ctx, { allowQs, allowNoTenant })
|
const allowQs = !!matches(ctx, allowQsOptions)
|
||||||
setGlobalDB(tenantId)
|
const tenantId = setTenantId(ctx, { allowQs, allowNoTenant })
|
||||||
|
setGlobalDB(tenantId)
|
||||||
|
const res = await next()
|
||||||
|
await closeTenancy()
|
||||||
|
return res
|
||||||
|
})
|
||||||
}
|
}
|
||||||
const destroyFn = async () => {
|
|
||||||
const db = getGlobalDB()
|
|
||||||
await closeDB(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ContextFactory.getMiddleware(updateCtxFn, destroyFn)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1533,7 +1533,7 @@ electron-to-chromium@^1.3.896:
|
||||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.900.tgz#5be2c5818a2a012c511b4b43e87b6ab7a296d4f5"
|
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.900.tgz#5be2c5818a2a012c511b4b43e87b6ab7a296d4f5"
|
||||||
integrity sha512-SuXbQD8D4EjsaBaJJxySHbC+zq8JrFfxtb4GIr4E9n1BcROyMcRrJCYQNpJ9N+Wjf5mFp7Wp0OHykd14JNEzzQ==
|
integrity sha512-SuXbQD8D4EjsaBaJJxySHbC+zq8JrFfxtb4GIr4E9n1BcROyMcRrJCYQNpJ9N+Wjf5mFp7Wp0OHykd14JNEzzQ==
|
||||||
|
|
||||||
emitter-listener@^1.0.1:
|
emitter-listener@^1.0.1, emitter-listener@^1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8"
|
resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8"
|
||||||
integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==
|
integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/bbui",
|
"name": "@budibase/bbui",
|
||||||
"description": "A UI solution used in the different Budibase projects.",
|
"description": "A UI solution used in the different Budibase projects.",
|
||||||
"version": "1.0.151-alpha.2",
|
"version": "1.0.155-alpha.0",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"module": "dist/bbui.es.js",
|
"module": "dist/bbui.es.js",
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
|
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
|
||||||
"@budibase/string-templates": "^1.0.151-alpha.2",
|
"@budibase/string-templates": "^1.0.155-alpha.0",
|
||||||
"@spectrum-css/actionbutton": "^1.0.1",
|
"@spectrum-css/actionbutton": "^1.0.1",
|
||||||
"@spectrum-css/actiongroup": "^1.0.1",
|
"@spectrum-css/actiongroup": "^1.0.1",
|
||||||
"@spectrum-css/avatar": "^3.0.2",
|
"@spectrum-css/avatar": "^3.0.2",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "1.0.151-alpha.2",
|
"version": "1.0.155-alpha.0",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -67,10 +67,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.0.151-alpha.2",
|
"@budibase/bbui": "^1.0.155-alpha.0",
|
||||||
"@budibase/client": "^1.0.151-alpha.2",
|
"@budibase/client": "^1.0.155-alpha.0",
|
||||||
"@budibase/frontend-core": "^1.0.151-alpha.2",
|
"@budibase/frontend-core": "^1.0.155-alpha.0",
|
||||||
"@budibase/string-templates": "^1.0.151-alpha.2",
|
"@budibase/string-templates": "^1.0.155-alpha.0",
|
||||||
"@sentry/browser": "5.19.1",
|
"@sentry/browser": "5.19.1",
|
||||||
"@spectrum-css/page": "^3.0.1",
|
"@spectrum-css/page": "^3.0.1",
|
||||||
"@spectrum-css/vars": "^3.0.1",
|
"@spectrum-css/vars": "^3.0.1",
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { Input, ModalContent, Modal, Body } from "@budibase/bbui"
|
import { Input, ModalContent, Modal, Body } from "@budibase/bbui"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
export let dynamicVariables
|
export let dynamicVariables
|
||||||
export let datasource
|
export let datasource
|
||||||
|
@ -35,6 +38,7 @@
|
||||||
name = null
|
name = null
|
||||||
binding = null
|
binding = null
|
||||||
dynamicVariables[copiedName] = copiedBinding
|
dynamicVariables[copiedName] = copiedBinding
|
||||||
|
dispatch("change", dynamicVariables)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
import AccessLevelSelect from "components/integration/AccessLevelSelect.svelte"
|
import AccessLevelSelect from "components/integration/AccessLevelSelect.svelte"
|
||||||
import DynamicVariableModal from "../../_components/DynamicVariableModal.svelte"
|
import DynamicVariableModal from "../../_components/DynamicVariableModal.svelte"
|
||||||
import Placeholder from "assets/bb-spaceship.svg"
|
import Placeholder from "assets/bb-spaceship.svg"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep, isEqual } from "lodash/fp"
|
||||||
import { RawRestBodyTypes } from "constants/backend"
|
import { RawRestBodyTypes } from "constants/backend"
|
||||||
|
|
||||||
let query, datasource
|
let query, datasource
|
||||||
|
@ -47,6 +47,7 @@
|
||||||
let response, schema, enabledHeaders
|
let response, schema, enabledHeaders
|
||||||
let authConfigId
|
let authConfigId
|
||||||
let dynamicVariables, addVariableModal, varBinding
|
let dynamicVariables, addVariableModal, varBinding
|
||||||
|
let baseQuery, baseDatasource, baseVariables
|
||||||
|
|
||||||
$: datasourceType = datasource?.source
|
$: datasourceType = datasource?.source
|
||||||
$: integrationInfo = $integrations[datasourceType]
|
$: integrationInfo = $integrations[datasourceType]
|
||||||
|
@ -62,6 +63,15 @@
|
||||||
$: hasSchema =
|
$: hasSchema =
|
||||||
Object.keys(schema || {}).length !== 0 ||
|
Object.keys(schema || {}).length !== 0 ||
|
||||||
Object.keys(query?.schema || {}).length !== 0
|
Object.keys(query?.schema || {}).length !== 0
|
||||||
|
$: baseQuery = !baseQuery ? cloneDeep(query) : baseQuery
|
||||||
|
$: baseDatasource = !baseDatasource ? cloneDeep(datasource) : baseDatasource
|
||||||
|
$: baseVariables = !baseVariables
|
||||||
|
? cloneDeep(dynamicVariables)
|
||||||
|
: baseVariables
|
||||||
|
$: hasChanged =
|
||||||
|
!isEqual(baseQuery, query) ||
|
||||||
|
!isEqual(baseDatasource, datasource) ||
|
||||||
|
!isEqual(baseVariables, dynamicVariables)
|
||||||
|
|
||||||
function getSelectedQuery() {
|
function getSelectedQuery() {
|
||||||
return cloneDeep(
|
return cloneDeep(
|
||||||
|
@ -120,6 +130,9 @@
|
||||||
datasource.config.dynamicVariables = rebuildVariables(saveId)
|
datasource.config.dynamicVariables = rebuildVariables(saveId)
|
||||||
datasource = await datasources.save(datasource)
|
datasource = await datasources.save(datasource)
|
||||||
}
|
}
|
||||||
|
baseQuery = query
|
||||||
|
baseDatasource = datasource
|
||||||
|
baseVariables = dynamicVariables
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notifications.error(`Error saving query`)
|
notifications.error(`Error saving query`)
|
||||||
}
|
}
|
||||||
|
@ -299,6 +312,7 @@
|
||||||
{dynamicVariables}
|
{dynamicVariables}
|
||||||
bind:binding={varBinding}
|
bind:binding={varBinding}
|
||||||
bind:this={addVariableModal}
|
bind:this={addVariableModal}
|
||||||
|
on:change={saveQuery}
|
||||||
/>
|
/>
|
||||||
{#if query && queryConfig}
|
{#if query && queryConfig}
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
|
@ -332,7 +346,7 @@
|
||||||
</div>
|
</div>
|
||||||
<Button primary disabled={!url} on:click={runQuery}>Send</Button>
|
<Button primary disabled={!url} on:click={runQuery}>Send</Button>
|
||||||
<Button
|
<Button
|
||||||
disabled={!query.name}
|
disabled={!query.name || !hasChanged}
|
||||||
cta
|
cta
|
||||||
on:click={saveQuery}
|
on:click={saveQuery}
|
||||||
tooltip={!hasSchema
|
tooltip={!hasSchema
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "1.0.151-alpha.2",
|
"version": "1.0.155-alpha.0",
|
||||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "1.0.151-alpha.2",
|
"version": "1.0.155-alpha.0",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"module": "dist/budibase-client.js",
|
"module": "dist/budibase-client.js",
|
||||||
"main": "dist/budibase-client.js",
|
"main": "dist/budibase-client.js",
|
||||||
|
@ -19,9 +19,9 @@
|
||||||
"dev:builder": "rollup -cw"
|
"dev:builder": "rollup -cw"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.0.151-alpha.2",
|
"@budibase/bbui": "^1.0.155-alpha.0",
|
||||||
"@budibase/frontend-core": "^1.0.151-alpha.2",
|
"@budibase/frontend-core": "^1.0.155-alpha.0",
|
||||||
"@budibase/string-templates": "^1.0.151-alpha.2",
|
"@budibase/string-templates": "^1.0.155-alpha.0",
|
||||||
"@spectrum-css/button": "^3.0.3",
|
"@spectrum-css/button": "^3.0.3",
|
||||||
"@spectrum-css/card": "^3.0.3",
|
"@spectrum-css/card": "^3.0.3",
|
||||||
"@spectrum-css/divider": "^1.0.3",
|
"@spectrum-css/divider": "^1.0.3",
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/frontend-core",
|
"name": "@budibase/frontend-core",
|
||||||
"version": "1.0.151-alpha.2",
|
"version": "1.0.155-alpha.0",
|
||||||
"description": "Budibase frontend core libraries used in builder and client",
|
"description": "Budibase frontend core libraries used in builder and client",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.0.151-alpha.2",
|
"@budibase/bbui": "^1.0.155-alpha.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"svelte": "^3.46.2"
|
"svelte": "^3.46.2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "1.0.151-alpha.2",
|
"version": "1.0.155-alpha.0",
|
||||||
"description": "Budibase Web Server",
|
"description": "Budibase Web Server",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -10,6 +10,7 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rimraf dist/ && tsc -p tsconfig.build.json && mv dist/src/* dist/ && rimraf dist/src/ && yarn postbuild",
|
"build": "rimraf dist/ && tsc -p tsconfig.build.json && mv dist/src/* dist/ && rimraf dist/src/ && yarn postbuild",
|
||||||
|
"debug": "yarn build && node --expose-gc --inspect=9222 dist/index.js",
|
||||||
"postbuild": "copyfiles -u 1 src/**/*.svelte dist/ && copyfiles -u 1 src/**/*.hbs dist/ && copyfiles -u 1 src/**/*.json dist/",
|
"postbuild": "copyfiles -u 1 src/**/*.svelte dist/ && copyfiles -u 1 src/**/*.hbs dist/ && copyfiles -u 1 src/**/*.json dist/",
|
||||||
"test": "jest --coverage --maxWorkers=2",
|
"test": "jest --coverage --maxWorkers=2",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
|
@ -68,10 +69,10 @@
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apidevtools/swagger-parser": "^10.0.3",
|
"@apidevtools/swagger-parser": "^10.0.3",
|
||||||
"@budibase/backend-core": "^1.0.151-alpha.2",
|
"@budibase/backend-core": "^1.0.155-alpha.0",
|
||||||
"@budibase/client": "^1.0.151-alpha.2",
|
"@budibase/client": "^1.0.155-alpha.0",
|
||||||
"@budibase/pro": "1.0.151-alpha.2",
|
"@budibase/pro": "1.0.155-alpha.0",
|
||||||
"@budibase/string-templates": "^1.0.151-alpha.2",
|
"@budibase/string-templates": "^1.0.155-alpha.0",
|
||||||
"@bull-board/api": "^3.7.0",
|
"@bull-board/api": "^3.7.0",
|
||||||
"@bull-board/koa": "^3.7.0",
|
"@bull-board/koa": "^3.7.0",
|
||||||
"@elastic/elasticsearch": "7.10.0",
|
"@elastic/elasticsearch": "7.10.0",
|
||||||
|
|
|
@ -31,7 +31,9 @@ export async function patch(ctx: any): Promise<any> {
|
||||||
return save(ctx)
|
return save(ctx)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const { row, table } = await pickApi(tableId).patch(ctx)
|
const { row, table } = await quotas.addQuery(() =>
|
||||||
|
pickApi(tableId).patch(ctx)
|
||||||
|
)
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.eventEmitter &&
|
ctx.eventEmitter &&
|
||||||
ctx.eventEmitter.emitRow(`row:update`, appId, row, table)
|
ctx.eventEmitter.emitRow(`row:update`, appId, row, table)
|
||||||
|
@ -42,7 +44,7 @@ export async function patch(ctx: any): Promise<any> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveRow = async (ctx: any) => {
|
export const save = async (ctx: any) => {
|
||||||
const appId = ctx.appId
|
const appId = ctx.appId
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
const body = ctx.request.body
|
const body = ctx.request.body
|
||||||
|
@ -51,7 +53,9 @@ const saveRow = async (ctx: any) => {
|
||||||
return patch(ctx)
|
return patch(ctx)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const { row, table } = await pickApi(tableId).save(ctx)
|
const { row, table } = await quotas.addRow(() =>
|
||||||
|
quotas.addQuery(() => pickApi(tableId).save(ctx))
|
||||||
|
)
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:save`, appId, row, table)
|
ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:save`, appId, row, table)
|
||||||
ctx.message = `${table.name} saved successfully`
|
ctx.message = `${table.name} saved successfully`
|
||||||
|
@ -61,14 +65,10 @@ const saveRow = async (ctx: any) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function save(ctx: any) {
|
|
||||||
await quotas.addRow(() => saveRow(ctx))
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchView(ctx: any) {
|
export async function fetchView(ctx: any) {
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
try {
|
try {
|
||||||
ctx.body = await pickApi(tableId).fetchView(ctx)
|
ctx.body = await quotas.addQuery(() => pickApi(tableId).fetchView(ctx))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.throw(400, err)
|
ctx.throw(400, err)
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,7 @@ export async function fetchView(ctx: any) {
|
||||||
export async function fetch(ctx: any) {
|
export async function fetch(ctx: any) {
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
try {
|
try {
|
||||||
ctx.body = await pickApi(tableId).fetch(ctx)
|
ctx.body = await quotas.addQuery(() => pickApi(tableId).fetch(ctx))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.throw(400, err)
|
ctx.throw(400, err)
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ export async function fetch(ctx: any) {
|
||||||
export async function find(ctx: any) {
|
export async function find(ctx: any) {
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
try {
|
try {
|
||||||
ctx.body = await pickApi(tableId).find(ctx)
|
ctx.body = await quotas.addQuery(() => pickApi(tableId).find(ctx))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.throw(400, err)
|
ctx.throw(400, err)
|
||||||
}
|
}
|
||||||
|
@ -98,14 +98,16 @@ export async function destroy(ctx: any) {
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
let response, row
|
let response, row
|
||||||
if (inputs.rows) {
|
if (inputs.rows) {
|
||||||
let { rows } = await pickApi(tableId).bulkDestroy(ctx)
|
let { rows } = await quotas.addQuery(() =>
|
||||||
|
pickApi(tableId).bulkDestroy(ctx)
|
||||||
|
)
|
||||||
await quotas.removeRows(rows.length)
|
await quotas.removeRows(rows.length)
|
||||||
response = rows
|
response = rows
|
||||||
for (let row of rows) {
|
for (let row of rows) {
|
||||||
ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:delete`, appId, row)
|
ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:delete`, appId, row)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let resp = await pickApi(tableId).destroy(ctx)
|
let resp = await quotas.addQuery(() => pickApi(tableId).destroy(ctx))
|
||||||
await quotas.removeRow()
|
await quotas.removeRow()
|
||||||
response = resp.response
|
response = resp.response
|
||||||
row = resp.row
|
row = resp.row
|
||||||
|
@ -121,7 +123,7 @@ export async function search(ctx: any) {
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
try {
|
try {
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.body = await pickApi(tableId).search(ctx)
|
ctx.body = await quotas.addQuery(() => pickApi(tableId).search(ctx))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.throw(400, err)
|
ctx.throw(400, err)
|
||||||
}
|
}
|
||||||
|
@ -139,7 +141,9 @@ export async function validate(ctx: any) {
|
||||||
export async function fetchEnrichedRow(ctx: any) {
|
export async function fetchEnrichedRow(ctx: any) {
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
try {
|
try {
|
||||||
ctx.body = await pickApi(tableId).fetchEnrichedRow(ctx)
|
ctx.body = await quotas.addQuery(() =>
|
||||||
|
pickApi(tableId).fetchEnrichedRow(ctx)
|
||||||
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.throw(400, err)
|
ctx.throw(400, err)
|
||||||
}
|
}
|
||||||
|
@ -148,7 +152,7 @@ export async function fetchEnrichedRow(ctx: any) {
|
||||||
export const exportRows = async (ctx: any) => {
|
export const exportRows = async (ctx: any) => {
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
try {
|
try {
|
||||||
ctx.body = await pickApi(tableId).exportRows(ctx)
|
ctx.body = await quotas.addQuery(() => pickApi(tableId).exportRows(ctx))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.throw(400, err)
|
ctx.throw(400, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ const setup = require("./utilities")
|
||||||
const { basicRow } = setup.structures
|
const { basicRow } = setup.structures
|
||||||
const { doInAppContext } = require("@budibase/backend-core/context")
|
const { doInAppContext } = require("@budibase/backend-core/context")
|
||||||
const { doInTenant } = require("@budibase/backend-core/tenancy")
|
const { doInTenant } = require("@budibase/backend-core/tenancy")
|
||||||
|
const { quotas, QuotaUsageType, StaticQuotaName, MonthlyQuotaName } = require("@budibase/pro")
|
||||||
|
|
||||||
// mock the fetch for the search system
|
// mock the fetch for the search system
|
||||||
jest.mock("node-fetch")
|
jest.mock("node-fetch")
|
||||||
|
@ -28,9 +29,29 @@ describe("/rows", () => {
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(status)
|
.expect(status)
|
||||||
|
|
||||||
|
const getRowUsage = async () => {
|
||||||
|
return config.doInContext(null, () => quotas.getCurrentUsageValue(QuotaUsageType.STATIC, StaticQuotaName.ROWS))
|
||||||
|
}
|
||||||
|
|
||||||
|
const getQueryUsage = async () => {
|
||||||
|
return config.doInContext(null, () => quotas.getCurrentUsageValue(QuotaUsageType.MONTHLY, MonthlyQuotaName.QUERIES))
|
||||||
|
}
|
||||||
|
|
||||||
|
const assertRowUsage = async (expected) => {
|
||||||
|
const usage = await getRowUsage()
|
||||||
|
expect(usage).toBe(expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
const assertQueryUsage = async (expected) => {
|
||||||
|
const usage = await getQueryUsage()
|
||||||
|
expect(usage).toBe(expected)
|
||||||
|
}
|
||||||
|
|
||||||
describe("save, load, update", () => {
|
describe("save, load, update", () => {
|
||||||
it("returns a success message when the row is created", async () => {
|
it("returns a success message when the row is created", async () => {
|
||||||
|
const rowUsage = await getRowUsage()
|
||||||
|
const queryUsage = await getQueryUsage()
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
.post(`/api/${row.tableId}/rows`)
|
.post(`/api/${row.tableId}/rows`)
|
||||||
.send(row)
|
.send(row)
|
||||||
|
@ -40,10 +61,14 @@ describe("/rows", () => {
|
||||||
expect(res.res.statusMessage).toEqual(`${table.name} saved successfully`)
|
expect(res.res.statusMessage).toEqual(`${table.name} saved successfully`)
|
||||||
expect(res.body.name).toEqual("Test Contact")
|
expect(res.body.name).toEqual("Test Contact")
|
||||||
expect(res.body._rev).toBeDefined()
|
expect(res.body._rev).toBeDefined()
|
||||||
|
await assertRowUsage(rowUsage + 1)
|
||||||
|
await assertQueryUsage(queryUsage + 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("updates a row successfully", async () => {
|
it("updates a row successfully", async () => {
|
||||||
const existing = await config.createRow()
|
const existing = await config.createRow()
|
||||||
|
const rowUsage = await getRowUsage()
|
||||||
|
const queryUsage = await getQueryUsage()
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
.post(`/api/${table._id}/rows`)
|
.post(`/api/${table._id}/rows`)
|
||||||
|
@ -59,10 +84,13 @@ describe("/rows", () => {
|
||||||
|
|
||||||
expect(res.res.statusMessage).toEqual(`${table.name} updated successfully.`)
|
expect(res.res.statusMessage).toEqual(`${table.name} updated successfully.`)
|
||||||
expect(res.body.name).toEqual("Updated Name")
|
expect(res.body.name).toEqual("Updated Name")
|
||||||
|
await assertRowUsage(rowUsage)
|
||||||
|
await assertQueryUsage(queryUsage + 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should load a row", async () => {
|
it("should load a row", async () => {
|
||||||
const existing = await config.createRow()
|
const existing = await config.createRow()
|
||||||
|
const queryUsage = await getQueryUsage()
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
.get(`/api/${table._id}/rows/${existing._id}`)
|
.get(`/api/${table._id}/rows/${existing._id}`)
|
||||||
|
@ -76,6 +104,7 @@ describe("/rows", () => {
|
||||||
_rev: existing._rev,
|
_rev: existing._rev,
|
||||||
type: "row",
|
type: "row",
|
||||||
})
|
})
|
||||||
|
await assertQueryUsage(queryUsage + 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should list all rows for given tableId", async () => {
|
it("should list all rows for given tableId", async () => {
|
||||||
|
@ -86,6 +115,7 @@ describe("/rows", () => {
|
||||||
}
|
}
|
||||||
await config.createRow()
|
await config.createRow()
|
||||||
await config.createRow(newRow)
|
await config.createRow(newRow)
|
||||||
|
const queryUsage = await getQueryUsage()
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
.get(`/api/${table._id}/rows`)
|
.get(`/api/${table._id}/rows`)
|
||||||
|
@ -96,15 +126,19 @@ describe("/rows", () => {
|
||||||
expect(res.body.length).toBe(2)
|
expect(res.body.length).toBe(2)
|
||||||
expect(res.body.find(r => r.name === newRow.name)).toBeDefined()
|
expect(res.body.find(r => r.name === newRow.name)).toBeDefined()
|
||||||
expect(res.body.find(r => r.name === row.name)).toBeDefined()
|
expect(res.body.find(r => r.name === row.name)).toBeDefined()
|
||||||
|
await assertQueryUsage(queryUsage + 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("load should return 404 when row does not exist", async () => {
|
it("load should return 404 when row does not exist", async () => {
|
||||||
await config.createRow()
|
await config.createRow()
|
||||||
|
const queryUsage = await getQueryUsage()
|
||||||
|
|
||||||
await request
|
await request
|
||||||
.get(`/api/${table._id}/rows/not-a-valid-id`)
|
.get(`/api/${table._id}/rows/not-a-valid-id`)
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(404)
|
.expect(404)
|
||||||
|
await assertQueryUsage(queryUsage) // no change
|
||||||
})
|
})
|
||||||
|
|
||||||
it("row values are coerced", async () => {
|
it("row values are coerced", async () => {
|
||||||
|
@ -202,6 +236,9 @@ describe("/rows", () => {
|
||||||
it("should update only the fields that are supplied", async () => {
|
it("should update only the fields that are supplied", async () => {
|
||||||
const existing = await config.createRow()
|
const existing = await config.createRow()
|
||||||
|
|
||||||
|
const rowUsage = await getRowUsage()
|
||||||
|
const queryUsage = await getQueryUsage()
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
.patch(`/api/${table._id}/rows`)
|
.patch(`/api/${table._id}/rows`)
|
||||||
.send({
|
.send({
|
||||||
|
@ -222,10 +259,15 @@ describe("/rows", () => {
|
||||||
|
|
||||||
expect(savedRow.body.description).toEqual(existing.description)
|
expect(savedRow.body.description).toEqual(existing.description)
|
||||||
expect(savedRow.body.name).toEqual("Updated Name")
|
expect(savedRow.body.name).toEqual("Updated Name")
|
||||||
|
await assertRowUsage(rowUsage)
|
||||||
|
await assertQueryUsage(queryUsage + 2) // account for the second load
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should throw an error when given improper types", async () => {
|
it("should throw an error when given improper types", async () => {
|
||||||
const existing = await config.createRow()
|
const existing = await config.createRow()
|
||||||
|
const rowUsage = await getRowUsage()
|
||||||
|
const queryUsage = await getQueryUsage()
|
||||||
|
|
||||||
await request
|
await request
|
||||||
.patch(`/api/${table._id}/rows`)
|
.patch(`/api/${table._id}/rows`)
|
||||||
.send({
|
.send({
|
||||||
|
@ -236,12 +278,18 @@ describe("/rows", () => {
|
||||||
})
|
})
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.expect(400)
|
.expect(400)
|
||||||
|
|
||||||
|
await assertRowUsage(rowUsage)
|
||||||
|
await assertQueryUsage(queryUsage)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("destroy", () => {
|
describe("destroy", () => {
|
||||||
it("should be able to delete a row", async () => {
|
it("should be able to delete a row", async () => {
|
||||||
const createdRow = await config.createRow(row)
|
const createdRow = await config.createRow(row)
|
||||||
|
const rowUsage = await getRowUsage()
|
||||||
|
const queryUsage = await getQueryUsage()
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
.delete(`/api/${table._id}/rows`)
|
.delete(`/api/${table._id}/rows`)
|
||||||
.send({
|
.send({
|
||||||
|
@ -253,11 +301,16 @@ describe("/rows", () => {
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
expect(res.body[0]._id).toEqual(createdRow._id)
|
expect(res.body[0]._id).toEqual(createdRow._id)
|
||||||
|
await assertRowUsage(rowUsage -1)
|
||||||
|
await assertQueryUsage(queryUsage +1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("validate", () => {
|
describe("validate", () => {
|
||||||
it("should return no errors on valid row", async () => {
|
it("should return no errors on valid row", async () => {
|
||||||
|
const rowUsage = await getRowUsage()
|
||||||
|
const queryUsage = await getQueryUsage()
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
.post(`/api/${table._id}/rows/validate`)
|
.post(`/api/${table._id}/rows/validate`)
|
||||||
.send({ name: "ivan" })
|
.send({ name: "ivan" })
|
||||||
|
@ -267,9 +320,14 @@ describe("/rows", () => {
|
||||||
|
|
||||||
expect(res.body.valid).toBe(true)
|
expect(res.body.valid).toBe(true)
|
||||||
expect(Object.keys(res.body.errors)).toEqual([])
|
expect(Object.keys(res.body.errors)).toEqual([])
|
||||||
|
await assertRowUsage(rowUsage)
|
||||||
|
await assertQueryUsage(queryUsage)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should errors on invalid row", async () => {
|
it("should errors on invalid row", async () => {
|
||||||
|
const rowUsage = await getRowUsage()
|
||||||
|
const queryUsage = await getQueryUsage()
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
.post(`/api/${table._id}/rows/validate`)
|
.post(`/api/${table._id}/rows/validate`)
|
||||||
.send({ name: 1 })
|
.send({ name: 1 })
|
||||||
|
@ -279,7 +337,8 @@ describe("/rows", () => {
|
||||||
|
|
||||||
expect(res.body.valid).toBe(false)
|
expect(res.body.valid).toBe(false)
|
||||||
expect(Object.keys(res.body.errors)).toEqual(["name"])
|
expect(Object.keys(res.body.errors)).toEqual(["name"])
|
||||||
|
await assertRowUsage(rowUsage)
|
||||||
|
await assertQueryUsage(queryUsage)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -287,6 +346,9 @@ describe("/rows", () => {
|
||||||
it("should be able to delete a bulk set of rows", async () => {
|
it("should be able to delete a bulk set of rows", async () => {
|
||||||
const row1 = await config.createRow()
|
const row1 = await config.createRow()
|
||||||
const row2 = await config.createRow()
|
const row2 = await config.createRow()
|
||||||
|
const rowUsage = await getRowUsage()
|
||||||
|
const queryUsage = await getQueryUsage()
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
.delete(`/api/${table._id}/rows`)
|
.delete(`/api/${table._id}/rows`)
|
||||||
.send({
|
.send({
|
||||||
|
@ -298,14 +360,20 @@ describe("/rows", () => {
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
||||||
expect(res.body.length).toEqual(2)
|
expect(res.body.length).toEqual(2)
|
||||||
await loadRow(row1._id, 404)
|
await loadRow(row1._id, 404)
|
||||||
|
await assertRowUsage(rowUsage - 2)
|
||||||
|
await assertQueryUsage(queryUsage +1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("fetchView", () => {
|
describe("fetchView", () => {
|
||||||
it("should be able to fetch tables contents via 'view'", async () => {
|
it("should be able to fetch tables contents via 'view'", async () => {
|
||||||
const row = await config.createRow()
|
const row = await config.createRow()
|
||||||
|
const rowUsage = await getRowUsage()
|
||||||
|
const queryUsage = await getQueryUsage()
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
.get(`/api/views/${table._id}`)
|
.get(`/api/views/${table._id}`)
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
|
@ -313,18 +381,29 @@ describe("/rows", () => {
|
||||||
.expect(200)
|
.expect(200)
|
||||||
expect(res.body.length).toEqual(1)
|
expect(res.body.length).toEqual(1)
|
||||||
expect(res.body[0]._id).toEqual(row._id)
|
expect(res.body[0]._id).toEqual(row._id)
|
||||||
|
await assertRowUsage(rowUsage)
|
||||||
|
await assertQueryUsage(queryUsage +1)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should throw an error if view doesn't exist", async () => {
|
it("should throw an error if view doesn't exist", async () => {
|
||||||
|
const rowUsage = await getRowUsage()
|
||||||
|
const queryUsage = await getQueryUsage()
|
||||||
|
|
||||||
await request
|
await request
|
||||||
.get(`/api/views/derp`)
|
.get(`/api/views/derp`)
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
.expect(404)
|
.expect(404)
|
||||||
|
|
||||||
|
await assertRowUsage(rowUsage)
|
||||||
|
await assertQueryUsage(queryUsage)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should be able to run on a view", async () => {
|
it("should be able to run on a view", async () => {
|
||||||
const view = await config.createView()
|
const view = await config.createView()
|
||||||
const row = await config.createRow()
|
const row = await config.createRow()
|
||||||
|
const rowUsage = await getRowUsage()
|
||||||
|
const queryUsage = await getQueryUsage()
|
||||||
|
|
||||||
const res = await request
|
const res = await request
|
||||||
.get(`/api/views/${view.name}`)
|
.get(`/api/views/${view.name}`)
|
||||||
.set(config.defaultHeaders())
|
.set(config.defaultHeaders())
|
||||||
|
@ -332,13 +411,12 @@ describe("/rows", () => {
|
||||||
.expect(200)
|
.expect(200)
|
||||||
expect(res.body.length).toEqual(1)
|
expect(res.body.length).toEqual(1)
|
||||||
expect(res.body[0]._id).toEqual(row._id)
|
expect(res.body[0]._id).toEqual(row._id)
|
||||||
|
|
||||||
|
await assertRowUsage(rowUsage)
|
||||||
|
await assertQueryUsage(queryUsage + 1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("user testing", () => {
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("fetchEnrichedRows", () => {
|
describe("fetchEnrichedRows", () => {
|
||||||
it("should allow enriching some linked rows", async () => {
|
it("should allow enriching some linked rows", async () => {
|
||||||
const { table, firstRow, secondRow } = await doInTenant(setup.structures.TENANT_ID, async () => {
|
const { table, firstRow, secondRow } = await doInTenant(setup.structures.TENANT_ID, async () => {
|
||||||
|
@ -356,6 +434,8 @@ describe("/rows", () => {
|
||||||
})
|
})
|
||||||
return { table, firstRow, secondRow }
|
return { table, firstRow, secondRow }
|
||||||
})
|
})
|
||||||
|
const rowUsage = await getRowUsage()
|
||||||
|
const queryUsage = await getQueryUsage()
|
||||||
|
|
||||||
// test basic enrichment
|
// test basic enrichment
|
||||||
const resBasic = await request
|
const resBasic = await request
|
||||||
|
@ -376,6 +456,8 @@ describe("/rows", () => {
|
||||||
expect(resEnriched.body.link[0]._id).toBe(firstRow._id)
|
expect(resEnriched.body.link[0]._id).toBe(firstRow._id)
|
||||||
expect(resEnriched.body.link[0].name).toBe("Test Contact")
|
expect(resEnriched.body.link[0].name).toBe("Test Contact")
|
||||||
expect(resEnriched.body.link[0].description).toBe("original description")
|
expect(resEnriched.body.link[0].description).toBe("original description")
|
||||||
|
await assertRowUsage(rowUsage)
|
||||||
|
await assertQueryUsage(queryUsage +2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -167,7 +167,6 @@ exports.screenValidator = () => {
|
||||||
_id: Joi.string().required(),
|
_id: Joi.string().required(),
|
||||||
_component: Joi.string().required(),
|
_component: Joi.string().required(),
|
||||||
_children: Joi.array().required(),
|
_children: Joi.array().required(),
|
||||||
_instanceName: Joi.string().required(),
|
|
||||||
_styles: Joi.object().required(),
|
_styles: Joi.object().required(),
|
||||||
type: OPTIONAL_STRING,
|
type: OPTIONAL_STRING,
|
||||||
table: OPTIONAL_STRING,
|
table: OPTIONAL_STRING,
|
||||||
|
|
|
@ -208,5 +208,10 @@ exports.AutomationErrors = {
|
||||||
FAILURE_CONDITION: "FAILURE_CONDITION_MET",
|
FAILURE_CONDITION: "FAILURE_CONDITION_MET",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.LoopStepTypes = {
|
||||||
|
ARRAY: "Array",
|
||||||
|
STRING: "String",
|
||||||
|
}
|
||||||
|
|
||||||
// pass through the list from the auth/core lib
|
// pass through the list from the auth/core lib
|
||||||
exports.ObjectStoreBuckets = ObjectStoreBuckets
|
exports.ObjectStoreBuckets = ObjectStoreBuckets
|
||||||
|
|
|
@ -21,6 +21,31 @@ type KnexQuery = Knex.QueryBuilder | Knex
|
||||||
const MIN_ISO_DATE = "0000-00-00T00:00:00.000Z"
|
const MIN_ISO_DATE = "0000-00-00T00:00:00.000Z"
|
||||||
const MAX_ISO_DATE = "9999-00-00T00:00:00.000Z"
|
const MAX_ISO_DATE = "9999-00-00T00:00:00.000Z"
|
||||||
|
|
||||||
|
function likeKey(client: string, key: string): string {
|
||||||
|
if (!key.includes(" ")) {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
let start: string, end: string
|
||||||
|
switch (client) {
|
||||||
|
case SqlClients.MY_SQL:
|
||||||
|
start = end = "`"
|
||||||
|
break
|
||||||
|
case SqlClients.ORACLE:
|
||||||
|
case SqlClients.POSTGRES:
|
||||||
|
start = end = '"'
|
||||||
|
break
|
||||||
|
case SqlClients.MS_SQL:
|
||||||
|
start = "["
|
||||||
|
end = "]"
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw "Unknown client"
|
||||||
|
}
|
||||||
|
const parts = key.split(".")
|
||||||
|
key = parts.map(part => `${start}${part}${end}`).join(".")
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
function parse(input: any) {
|
function parse(input: any) {
|
||||||
if (Array.isArray(input)) {
|
if (Array.isArray(input)) {
|
||||||
return JSON.stringify(input)
|
return JSON.stringify(input)
|
||||||
|
@ -125,7 +150,9 @@ class InternalBuilder {
|
||||||
} else {
|
} else {
|
||||||
const rawFnc = `${fnc}Raw`
|
const rawFnc = `${fnc}Raw`
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
query = query[rawFnc](`LOWER(${key}) LIKE ?`, [`%${value}%`])
|
query = query[rawFnc](`LOWER(${likeKey(this.client, key)}) LIKE ?`, [
|
||||||
|
`%${value}%`,
|
||||||
|
])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ const { DocumentTypes } = require("../db/utils")
|
||||||
const { doInTenant } = require("@budibase/backend-core/tenancy")
|
const { doInTenant } = require("@budibase/backend-core/tenancy")
|
||||||
const { definitions: triggerDefs } = require("../automations/triggerInfo")
|
const { definitions: triggerDefs } = require("../automations/triggerInfo")
|
||||||
const { doInAppContext, getAppDB } = require("@budibase/backend-core/context")
|
const { doInAppContext, getAppDB } = require("@budibase/backend-core/context")
|
||||||
const { AutomationErrors } = require("../constants")
|
const { AutomationErrors, LoopStepTypes } = require("../constants")
|
||||||
const FILTER_STEP_ID = actions.ACTION_DEFINITIONS.FILTER.stepId
|
const FILTER_STEP_ID = actions.ACTION_DEFINITIONS.FILTER.stepId
|
||||||
const LOOP_STEP_ID = actions.ACTION_DEFINITIONS.LOOP.stepId
|
const LOOP_STEP_ID = actions.ACTION_DEFINITIONS.LOOP.stepId
|
||||||
|
|
||||||
|
@ -17,6 +17,41 @@ const STOPPED_STATUS = { success: false, status: "STOPPED" }
|
||||||
const { cloneDeep } = require("lodash/fp")
|
const { cloneDeep } = require("lodash/fp")
|
||||||
const env = require("../environment")
|
const env = require("../environment")
|
||||||
|
|
||||||
|
function typecastForLooping(loopStep, input) {
|
||||||
|
if (!input || !input.binding) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const isArray = Array.isArray(input.binding),
|
||||||
|
isString = typeof input.binding === "string"
|
||||||
|
try {
|
||||||
|
switch (loopStep.inputs.option) {
|
||||||
|
case LoopStepTypes.ARRAY:
|
||||||
|
if (isString) {
|
||||||
|
return JSON.parse(input.binding)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case LoopStepTypes.STRING:
|
||||||
|
if (isArray) {
|
||||||
|
return input.binding.join(",")
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error("Unable to cast to correct type")
|
||||||
|
}
|
||||||
|
return input.binding
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLoopIterations(loopStep, input) {
|
||||||
|
const binding = typecastForLooping(loopStep, input)
|
||||||
|
if (!loopStep || !binding) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return Array.isArray(binding)
|
||||||
|
? binding.length
|
||||||
|
: automationUtils.stringSplit(binding).length
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The automation orchestrator is a class responsible for executing automations.
|
* The automation orchestrator is a class responsible for executing automations.
|
||||||
* It handles the context of the automation and makes sure each step gets the correct
|
* It handles the context of the automation and makes sure each step gets the correct
|
||||||
|
@ -107,7 +142,9 @@ class Orchestrator {
|
||||||
let loopSteps = []
|
let loopSteps = []
|
||||||
for (let step of automation.definition.steps) {
|
for (let step of automation.definition.steps) {
|
||||||
stepCount++
|
stepCount++
|
||||||
let input
|
let input,
|
||||||
|
iterations = 1,
|
||||||
|
iterationCount = 0
|
||||||
if (step.stepId === LOOP_STEP_ID) {
|
if (step.stepId === LOOP_STEP_ID) {
|
||||||
loopStep = step
|
loopStep = step
|
||||||
loopStepNumber = stepCount
|
loopStepNumber = stepCount
|
||||||
|
@ -116,13 +153,9 @@ class Orchestrator {
|
||||||
|
|
||||||
if (loopStep) {
|
if (loopStep) {
|
||||||
input = await processObject(loopStep.inputs, this._context)
|
input = await processObject(loopStep.inputs, this._context)
|
||||||
|
iterations = getLoopIterations(loopStep, input)
|
||||||
}
|
}
|
||||||
let iterations = loopStep
|
|
||||||
? Array.isArray(input.binding)
|
|
||||||
? input.binding.length
|
|
||||||
: automationUtils.stringSplit(input.binding).length
|
|
||||||
: 1
|
|
||||||
let iterationCount = 0
|
|
||||||
for (let index = 0; index < iterations; index++) {
|
for (let index = 0; index < iterations; index++) {
|
||||||
let originalStepInput = cloneDeep(step.inputs)
|
let originalStepInput = cloneDeep(step.inputs)
|
||||||
|
|
||||||
|
@ -132,18 +165,11 @@ class Orchestrator {
|
||||||
loopStep.inputs,
|
loopStep.inputs,
|
||||||
cloneDeep(this._context)
|
cloneDeep(this._context)
|
||||||
)
|
)
|
||||||
newInput = automationUtils.cleanInputValues(
|
|
||||||
newInput,
|
|
||||||
loopStep.schema.inputs
|
|
||||||
)
|
|
||||||
|
|
||||||
let tempOutput = { items: loopSteps, iterations: iterationCount }
|
let tempOutput = { items: loopSteps, iterations: iterationCount }
|
||||||
if (
|
try {
|
||||||
(loopStep.inputs.option === "Array" &&
|
newInput.binding = typecastForLooping(loopStep, newInput)
|
||||||
!Array.isArray(newInput.binding)) ||
|
} catch (err) {
|
||||||
(loopStep.inputs.option === "String" &&
|
|
||||||
typeof newInput.binding !== "string")
|
|
||||||
) {
|
|
||||||
this.updateContextAndOutput(loopStepNumber, step, tempOutput, {
|
this.updateContextAndOutput(loopStepNumber, step, tempOutput, {
|
||||||
status: AutomationErrors.INCORRECT_TYPE,
|
status: AutomationErrors.INCORRECT_TYPE,
|
||||||
success: false,
|
success: false,
|
||||||
|
@ -205,21 +231,13 @@ class Orchestrator {
|
||||||
}
|
}
|
||||||
|
|
||||||
let isFailure = false
|
let isFailure = false
|
||||||
if (
|
const currentItem = this._context.steps[loopStepNumber]?.currentItem
|
||||||
typeof this._context.steps[loopStepNumber]?.currentItem === "object"
|
if (currentItem && typeof currentItem === "object") {
|
||||||
) {
|
isFailure = Object.keys(currentItem).some(value => {
|
||||||
isFailure = Object.keys(
|
return currentItem[value] === loopStep.inputs.failure
|
||||||
this._context.steps[loopStepNumber].currentItem
|
|
||||||
).some(value => {
|
|
||||||
return (
|
|
||||||
this._context.steps[loopStepNumber].currentItem[value] ===
|
|
||||||
loopStep.inputs.failure
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
isFailure =
|
isFailure = currentItem && currentItem === loopStep.inputs.failure
|
||||||
this._context.steps[loopStepNumber]?.currentItem ===
|
|
||||||
loopStep.inputs.failure
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFailure) {
|
if (isFailure) {
|
||||||
|
|
|
@ -1014,10 +1014,10 @@
|
||||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||||
|
|
||||||
"@budibase/backend-core@1.0.147":
|
"@budibase/backend-core@1.0.150":
|
||||||
version "1.0.147"
|
version "1.0.150"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.147.tgz#fc93a803e97e304170b33bd28b4f5deda32bec18"
|
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.150.tgz#a48beb1e38f5e3e09473235fd2b7f35a05a15342"
|
||||||
integrity sha512-0GcF9G/tTAsko9g352MB8K3EtMTVb7v7Av6RdsYyKBdVtOD42HKFzSOD0n/RQUL4v70YByiu99zJAB6z1m1Pcg==
|
integrity sha512-ceQVPnypKFurQMJgghky+MxtiF3x4b7rIzYhXv6Y+QfIb7IdY6wvv9o+dD3GPwNZJCRFSFizPR4YLBXdL+2+yw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@techpass/passport-openidconnect" "^0.3.0"
|
"@techpass/passport-openidconnect" "^0.3.0"
|
||||||
aws-sdk "^2.901.0"
|
aws-sdk "^2.901.0"
|
||||||
|
@ -1091,12 +1091,12 @@
|
||||||
svelte-flatpickr "^3.2.3"
|
svelte-flatpickr "^3.2.3"
|
||||||
svelte-portal "^1.0.0"
|
svelte-portal "^1.0.0"
|
||||||
|
|
||||||
"@budibase/pro@1.0.147":
|
"@budibase/pro@1.0.150":
|
||||||
version "1.0.147"
|
version "1.0.150"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.147.tgz#c1ec9d92ffaa40285a83c57ddbec1f32802cd9a1"
|
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.150.tgz#06384561b67130bb2a93f0119921bd35d2be245c"
|
||||||
integrity sha512-e4im6Byqeeit/QFHkVhVdRmgIlJJY/W06cQrOuiG/1ngO4PGnXeJqtjQjHVfvchD5Xi3y1MgQ4AtH2Bzb5LEhw==
|
integrity sha512-Wlam8b5nhSeuNEj5Bb3ro1DKGX81mxxsjKMmxT4rbTqZrzuHSlTkxJ59IvUQqKuyp+cqfP6Rq/i4UXrhK0cg9Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/backend-core" "1.0.147"
|
"@budibase/backend-core" "1.0.150"
|
||||||
node-fetch "^2.6.1"
|
node-fetch "^2.6.1"
|
||||||
|
|
||||||
"@budibase/standard-components@^0.9.139":
|
"@budibase/standard-components@^0.9.139":
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/string-templates",
|
"name": "@budibase/string-templates",
|
||||||
"version": "1.0.151-alpha.2",
|
"version": "1.0.155-alpha.0",
|
||||||
"description": "Handlebars wrapper for Budibase templating.",
|
"description": "Handlebars wrapper for Budibase templating.",
|
||||||
"main": "src/index.cjs",
|
"main": "src/index.cjs",
|
||||||
"module": "dist/bundle.mjs",
|
"module": "dist/bundle.mjs",
|
||||||
|
|
|
@ -13,6 +13,16 @@ const HTML_SWAPS = {
|
||||||
">": ">",
|
">": ">",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isObject(value) {
|
||||||
|
if (value == null || typeof value !== "object") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
value.toString() === "[object Object]" ||
|
||||||
|
(value.length > 0 && typeof value[0] === "object")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const HELPERS = [
|
const HELPERS = [
|
||||||
// external helpers
|
// external helpers
|
||||||
new Helper(HelperFunctionNames.OBJECT, value => {
|
new Helper(HelperFunctionNames.OBJECT, value => {
|
||||||
|
@ -22,11 +32,7 @@ const HELPERS = [
|
||||||
new Helper(HelperFunctionNames.JS, processJS, false),
|
new Helper(HelperFunctionNames.JS, processJS, false),
|
||||||
// this help is applied to all statements
|
// this help is applied to all statements
|
||||||
new Helper(HelperFunctionNames.ALL, (value, { __opts }) => {
|
new Helper(HelperFunctionNames.ALL, (value, { __opts }) => {
|
||||||
if (
|
if (isObject(value)) {
|
||||||
value != null &&
|
|
||||||
typeof value === "object" &&
|
|
||||||
value.toString() === "[object Object]"
|
|
||||||
) {
|
|
||||||
return new SafeString(JSON.stringify(value))
|
return new SafeString(JSON.stringify(value))
|
||||||
}
|
}
|
||||||
// null/undefined values produce bad results
|
// null/undefined values produce bad results
|
||||||
|
|
|
@ -64,9 +64,10 @@ module.exports.processors = [
|
||||||
return statement
|
return statement
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const testHelper = possibleHelper.trim().toLowerCase()
|
||||||
if (
|
if (
|
||||||
!noHelpers &&
|
!noHelpers &&
|
||||||
HelperNames().some(option => option.includes(possibleHelper))
|
HelperNames().some(option => testHelper === option.toLowerCase())
|
||||||
) {
|
) {
|
||||||
insideStatement = `(${insideStatement})`
|
insideStatement = `(${insideStatement})`
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,6 +106,16 @@ describe("Test that the object processing works correctly", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("check returning objects", () => {
|
||||||
|
it("should handle an array of objects", async () => {
|
||||||
|
const json = [{a: 1},{a: 2}]
|
||||||
|
const output = await processString("{{ testing }}", {
|
||||||
|
testing: json
|
||||||
|
})
|
||||||
|
expect(output).toEqual(JSON.stringify(json))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("check the utility functions", () => {
|
describe("check the utility functions", () => {
|
||||||
it("should return false for an invalid template string", () => {
|
it("should return false for an invalid template string", () => {
|
||||||
const valid = isValid("{{ table1.thing prop }}")
|
const valid = isValid("{{ table1.thing prop }}")
|
||||||
|
|
|
@ -30,6 +30,11 @@ describe("Handling context properties with spaces in their name", () => {
|
||||||
})
|
})
|
||||||
expect(output).toBe("testcase 1")
|
expect(output).toBe("testcase 1")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should allow the use of a", async () => {
|
||||||
|
const output = await processString("{{ a }}", { a: 1 })
|
||||||
|
expect(output).toEqual("1")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("attempt some complex problems", () => {
|
describe("attempt some complex problems", () => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/worker",
|
"name": "@budibase/worker",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "1.0.151-alpha.2",
|
"version": "1.0.155-alpha.0",
|
||||||
"description": "Budibase background service",
|
"description": "Budibase background service",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -31,9 +31,9 @@
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/backend-core": "^1.0.151-alpha.2",
|
"@budibase/backend-core": "^1.0.155-alpha.0",
|
||||||
"@budibase/pro": "1.0.151-alpha.2",
|
"@budibase/pro": "1.0.155-alpha.0",
|
||||||
"@budibase/string-templates": "^1.0.151-alpha.2",
|
"@budibase/string-templates": "^1.0.155-alpha.0",
|
||||||
"@koa/router": "^8.0.0",
|
"@koa/router": "^8.0.0",
|
||||||
"@sentry/node": "6.17.7",
|
"@sentry/node": "6.17.7",
|
||||||
"@techpass/passport-openidconnect": "^0.3.0",
|
"@techpass/passport-openidconnect": "^0.3.0",
|
||||||
|
|
|
@ -293,10 +293,10 @@
|
||||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||||
|
|
||||||
"@budibase/backend-core@1.0.147":
|
"@budibase/backend-core@1.0.150":
|
||||||
version "1.0.147"
|
version "1.0.150"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.147.tgz#fc93a803e97e304170b33bd28b4f5deda32bec18"
|
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-1.0.150.tgz#a48beb1e38f5e3e09473235fd2b7f35a05a15342"
|
||||||
integrity sha512-0GcF9G/tTAsko9g352MB8K3EtMTVb7v7Av6RdsYyKBdVtOD42HKFzSOD0n/RQUL4v70YByiu99zJAB6z1m1Pcg==
|
integrity sha512-ceQVPnypKFurQMJgghky+MxtiF3x4b7rIzYhXv6Y+QfIb7IdY6wvv9o+dD3GPwNZJCRFSFizPR4YLBXdL+2+yw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@techpass/passport-openidconnect" "^0.3.0"
|
"@techpass/passport-openidconnect" "^0.3.0"
|
||||||
aws-sdk "^2.901.0"
|
aws-sdk "^2.901.0"
|
||||||
|
@ -321,12 +321,12 @@
|
||||||
uuid "^8.3.2"
|
uuid "^8.3.2"
|
||||||
zlib "^1.0.5"
|
zlib "^1.0.5"
|
||||||
|
|
||||||
"@budibase/pro@1.0.147":
|
"@budibase/pro@1.0.150":
|
||||||
version "1.0.147"
|
version "1.0.150"
|
||||||
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.147.tgz#c1ec9d92ffaa40285a83c57ddbec1f32802cd9a1"
|
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-1.0.150.tgz#06384561b67130bb2a93f0119921bd35d2be245c"
|
||||||
integrity sha512-e4im6Byqeeit/QFHkVhVdRmgIlJJY/W06cQrOuiG/1ngO4PGnXeJqtjQjHVfvchD5Xi3y1MgQ4AtH2Bzb5LEhw==
|
integrity sha512-Wlam8b5nhSeuNEj5Bb3ro1DKGX81mxxsjKMmxT4rbTqZrzuHSlTkxJ59IvUQqKuyp+cqfP6Rq/i4UXrhK0cg9Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@budibase/backend-core" "1.0.147"
|
"@budibase/backend-core" "1.0.150"
|
||||||
node-fetch "^2.6.1"
|
node-fetch "^2.6.1"
|
||||||
|
|
||||||
"@cspotcode/source-map-consumer@0.8.0":
|
"@cspotcode/source-map-consumer@0.8.0":
|
||||||
|
|
Loading…
Reference in a new issue