mirror of https://github.com/Budibase/budibase.git
141 changed files with 3476 additions and 819 deletions
@ -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") |
||||
class FunctionContext { |
|
||||
static getMiddleware( |
function getContextStorage(namespace) { |
||||
updateCtxFn = null, |
if (namespace && namespace.active) { |
||||
destroyFn = null, |
let contextData = namespace.active |
||||
contextName = "session" |
delete contextData.id |
||||
) { |
delete contextData._ns_name |
||||
const namespace = this.createNamespace(contextName) |
return contextData |
||||
|
|
||||
return async function (ctx, next) { |
|
||||
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) |
|
||||
} |
|
||||
}) |
|
||||
}) |
|
||||
) |
|
||||
} |
|
||||
} |
} |
||||
|
return {} |
||||
|
} |
||||
|
|
||||
static run(callback, contextName = "session") { |
class FunctionContext { |
||||
const namespace = this.createNamespace(contextName) |
static run(callback) { |
||||
|
return MAIN_CTX.runAndReturn(async () => { |
||||
return namespace.runAndReturn(callback) |
const namespaceId = newid() |
||||
|
MAIN_CTX.set(REQUEST_ID_KEY, namespaceId) |
||||
|
const namespace = cls.createNamespace(namespaceId) |
||||
|
let response = await namespace.runAndReturn(callback) |
||||
|
cls.destroyNamespace(namespaceId) |
||||
|
return response |
||||
|
}) |
||||
} |
} |
||||
|
|
||||
static setOnContext(key, value, contextName = "session") { |
static setOnContext(key, value) { |
||||
const namespace = this.createNamespace(contextName) |
const namespaceId = MAIN_CTX.get(REQUEST_ID_KEY) |
||||
|
const namespace = cls.getNamespace(namespaceId) |
||||
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 |
||||
|
|||||
@ -0,0 +1,194 @@ |
|||||
|
require("../../tests/utilities/TestConfiguration"); |
||||
|
const { |
||||
|
generateAppID, |
||||
|
getDevelopmentAppID, |
||||
|
getProdAppID, |
||||
|
isDevAppID, |
||||
|
isProdAppID, |
||||
|
getPlatformUrl, |
||||
|
getScopedConfig |
||||
|
} = require("../utils") |
||||
|
const tenancy = require("../../tenancy"); |
||||
|
const { Configs, DEFAULT_TENANT_ID } = require("../../constants"); |
||||
|
const env = require("../../environment") |
||||
|
|
||||
|
describe("utils", () => { |
||||
|
describe("app ID manipulation", () => { |
||||
|
|
||||
|
function getID() { |
||||
|
const appId = generateAppID() |
||||
|
const split = appId.split("_") |
||||
|
const uuid = split[split.length - 1] |
||||
|
const devAppId = `app_dev_${uuid}` |
||||
|
return { appId, devAppId, split, uuid } |
||||
|
} |
||||
|
|
||||
|
it("should be able to generate a new app ID", () => { |
||||
|
expect(generateAppID().startsWith("app_")).toEqual(true) |
||||
|
}) |
||||
|
|
||||
|
it("should be able to convert a production app ID to development", () => { |
||||
|
const { appId, uuid } = getID() |
||||
|
expect(getDevelopmentAppID(appId)).toEqual(`app_dev_${uuid}`) |
||||
|
}) |
||||
|
|
||||
|
it("should be able to convert a development app ID to development", () => { |
||||
|
const { devAppId, uuid } = getID() |
||||
|
expect(getDevelopmentAppID(devAppId)).toEqual(`app_dev_${uuid}`) |
||||
|
}) |
||||
|
|
||||
|
it("should be able to convert a development ID to a production", () => { |
||||
|
const { devAppId, uuid } = getID() |
||||
|
expect(getProdAppID(devAppId)).toEqual(`app_${uuid}`) |
||||
|
}) |
||||
|
|
||||
|
it("should be able to convert a production ID to production", () => { |
||||
|
const { appId, uuid } = getID() |
||||
|
expect(getProdAppID(appId)).toEqual(`app_${uuid}`) |
||||
|
}) |
||||
|
|
||||
|
it("should be able to confirm dev app ID is development", () => { |
||||
|
const { devAppId } = getID() |
||||
|
expect(isDevAppID(devAppId)).toEqual(true) |
||||
|
}) |
||||
|
|
||||
|
it("should be able to confirm prod app ID is not development", () => { |
||||
|
const { appId } = getID() |
||||
|
expect(isDevAppID(appId)).toEqual(false) |
||||
|
}) |
||||
|
|
||||
|
it("should be able to confirm prod app ID is prod", () => { |
||||
|
const { appId } = getID() |
||||
|
expect(isProdAppID(appId)).toEqual(true) |
||||
|
}) |
||||
|
|
||||
|
it("should be able to confirm dev app ID is not prod", () => { |
||||
|
const { devAppId } = getID() |
||||
|
expect(isProdAppID(devAppId)).toEqual(false) |
||||
|
}) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
const DB_URL = "http://dburl.com" |
||||
|
const DEFAULT_URL = "http://localhost:10000" |
||||
|
const ENV_URL = "http://env.com" |
||||
|
|
||||
|
const setDbPlatformUrl = async () => { |
||||
|
const db = tenancy.getGlobalDB() |
||||
|
db.put({ |
||||
|
_id: "config_settings", |
||||
|
type: Configs.SETTINGS, |
||||
|
config: { |
||||
|
platformUrl: DB_URL |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
const clearSettingsConfig = async () => { |
||||
|
await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => { |
||||
|
const db = tenancy.getGlobalDB() |
||||
|
try { |
||||
|
const config = await db.get("config_settings") |
||||
|
await db.remove("config_settings", config._rev) |
||||
|
} catch (e) { |
||||
|
if (e.status !== 404) { |
||||
|
throw e |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
describe("getPlatformUrl", () => { |
||||
|
describe("self host", () => { |
||||
|
|
||||
|
beforeEach(async () => { |
||||
|
env._set("SELF_HOST", 1) |
||||
|
await clearSettingsConfig() |
||||
|
}) |
||||
|
|
||||
|
it("gets the default url", async () => { |
||||
|
await tenancy.doInTenant(null, async () => { |
||||
|
const url = await getPlatformUrl() |
||||
|
expect(url).toBe(DEFAULT_URL) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
it("gets the platform url from the environment", async () => { |
||||
|
await tenancy.doInTenant(null, async () => { |
||||
|
env._set("PLATFORM_URL", ENV_URL) |
||||
|
const url = await getPlatformUrl() |
||||
|
expect(url).toBe(ENV_URL) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
it("gets the platform url from the database", async () => { |
||||
|
await tenancy.doInTenant(null, async () => { |
||||
|
await setDbPlatformUrl() |
||||
|
const url = await getPlatformUrl() |
||||
|
expect(url).toBe(DB_URL) |
||||
|
}) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
|
||||
|
describe("cloud", () => { |
||||
|
const TENANT_AWARE_URL = "http://default.env.com" |
||||
|
|
||||
|
beforeEach(async () => { |
||||
|
env._set("SELF_HOSTED", 0) |
||||
|
env._set("MULTI_TENANCY", 1) |
||||
|
env._set("PLATFORM_URL", ENV_URL) |
||||
|
await clearSettingsConfig() |
||||
|
}) |
||||
|
|
||||
|
it("gets the platform url from the environment without tenancy", async () => { |
||||
|
await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => { |
||||
|
const url = await getPlatformUrl({ tenantAware: false }) |
||||
|
expect(url).toBe(ENV_URL) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
it("gets the platform url from the environment with tenancy", async () => { |
||||
|
await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => { |
||||
|
const url = await getPlatformUrl() |
||||
|
expect(url).toBe(TENANT_AWARE_URL) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
it("never gets the platform url from the database", async () => { |
||||
|
await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => { |
||||
|
await setDbPlatformUrl() |
||||
|
const url = await getPlatformUrl() |
||||
|
expect(url).toBe(TENANT_AWARE_URL) |
||||
|
}) |
||||
|
}) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
describe("getScopedConfig", () => { |
||||
|
describe("settings config", () => { |
||||
|
|
||||
|
beforeEach(async () => { |
||||
|
env._set("SELF_HOSTED", 1) |
||||
|
env._set("PLATFORM_URL", "") |
||||
|
await clearSettingsConfig() |
||||
|
}) |
||||
|
|
||||
|
it("returns the platform url with an existing config", async () => { |
||||
|
await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => { |
||||
|
await setDbPlatformUrl() |
||||
|
const db = tenancy.getGlobalDB() |
||||
|
const config = await getScopedConfig(db, { type: Configs.SETTINGS }) |
||||
|
expect(config.platformUrl).toBe(DB_URL) |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
it("returns the platform url without an existing config", async () => { |
||||
|
await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => { |
||||
|
const db = tenancy.getGlobalDB() |
||||
|
const config = await getScopedConfig(db, { type: Configs.SETTINGS }) |
||||
|
expect(config.platformUrl).toBe(DEFAULT_URL) |
||||
|
}) |
||||
|
}) |
||||
|
}) |
||||
|
}) |
||||
@ -1,7 +1,10 @@ |
|||||
export * from "./publishers" |
|
||||
|
|
||||
import { processors } from "./processors" |
import { processors } from "./processors" |
||||
|
export * from "./publishers" |
||||
|
|
||||
export const shutdown = () => { |
export const shutdown = () => { |
||||
processors.shutdown() |
processors.shutdown() |
||||
} |
} |
||||
|
|
||||
|
export const analyticsEnabled = () => { |
||||
|
return true |
||||
|
} |
||||
|
|||||
@ -0,0 +1,68 @@ |
|||||
|
<script> |
||||
|
import "@spectrum-css/fieldgroup/dist/index-vars.css" |
||||
|
import "@spectrum-css/radio/dist/index-vars.css" |
||||
|
import { createEventDispatcher } from "svelte" |
||||
|
|
||||
|
export let direction = "vertical" |
||||
|
export let value = [] |
||||
|
export let options = [] |
||||
|
export let error = null |
||||
|
export let disabled = false |
||||
|
export let getOptionLabel = option => option |
||||
|
export let getOptionValue = option => option |
||||
|
|
||||
|
const dispatch = createEventDispatcher() |
||||
|
const onChange = e => { |
||||
|
let tempValue = value |
||||
|
let isChecked = e.target.checked |
||||
|
if (!tempValue.includes(e.target.value) && isChecked) { |
||||
|
tempValue.push(e.target.value) |
||||
|
} |
||||
|
value = tempValue |
||||
|
dispatch( |
||||
|
"change", |
||||
|
tempValue.filter(val => val !== e.target.value || isChecked) |
||||
|
) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<div class={`spectrum-FieldGroup spectrum-FieldGroup--${direction}`}> |
||||
|
{#if options && Array.isArray(options)} |
||||
|
{#each options as option} |
||||
|
<div |
||||
|
title={getOptionLabel(option)} |
||||
|
class="spectrum-Checkbox spectrum-FieldGroup-item" |
||||
|
class:is-invalid={!!error} |
||||
|
> |
||||
|
<label |
||||
|
class="spectrum-Checkbox spectrum-Checkbox--sizeM spectrum-FieldGroup-item" |
||||
|
> |
||||
|
<input |
||||
|
on:change={onChange} |
||||
|
value={getOptionValue(option)} |
||||
|
type="checkbox" |
||||
|
class="spectrum-Checkbox-input" |
||||
|
{disabled} |
||||
|
checked={value.includes(getOptionValue(option))} |
||||
|
/> |
||||
|
<span class="spectrum-Checkbox-box"> |
||||
|
<svg |
||||
|
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Checkbox-checkmark" |
||||
|
focusable="false" |
||||
|
aria-hidden="true" |
||||
|
> |
||||
|
<use xlink:href="#spectrum-css-icon-Checkmark100" /> |
||||
|
</svg> |
||||
|
</span> |
||||
|
<span class="spectrum-Checkbox-label">{getOptionLabel(option)}</span> |
||||
|
</label> |
||||
|
</div> |
||||
|
{/each} |
||||
|
{/if} |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.spectrum-Checkbox-input { |
||||
|
opacity: 0; |
||||
|
} |
||||
|
</style> |
||||
@ -1,11 +1,19 @@ |
|||||
{ |
{ |
||||
"baseUrl": "http://localhost:4100", |
"baseUrl": "http://localhost:4100", |
||||
"video": false, |
"video": true, |
||||
"projectId": "bmbemn", |
"projectId": "bmbemn", |
||||
|
"reporter": "cypress-multi-reporters", |
||||
|
"reporterOptions": { |
||||
|
"configFile": "reporterConfig.json" |
||||
|
}, |
||||
"env": { |
"env": { |
||||
"PORT": "4100", |
"PORT": "4100", |
||||
"WORKER_PORT": "4200", |
"WORKER_PORT": "4200", |
||||
"JWT_SECRET": "test", |
"JWT_SECRET": "test", |
||||
"HOST_IP": "" |
"HOST_IP": "" |
||||
|
}, |
||||
|
"retries": { |
||||
|
"runMode": 2, |
||||
|
"openMode": 0 |
||||
} |
} |
||||
} |
} |
||||
|
|||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,81 @@ |
|||||
|
import filterTests from "../../../support/filterTests" |
||||
|
|
||||
|
filterTests(["all"], () => { |
||||
|
context("IT Ticketing System Template Functionality", () => { |
||||
|
const templateName = "IT Ticketing System" |
||||
|
const templateNameParsed = templateName.toLowerCase().replace(/\s+/g, '-') |
||||
|
|
||||
|
before(() => { |
||||
|
cy.login() |
||||
|
cy.deleteApp(templateName) |
||||
|
cy.visit(`${Cypress.config().baseUrl}/builder`, { |
||||
|
onBeforeLoad(win) { |
||||
|
cy.stub(win, 'open') |
||||
|
} |
||||
|
}) |
||||
|
cy.wait(2000) |
||||
|
cy.templateNavigation() |
||||
|
}) |
||||
|
|
||||
|
it("should create and publish app with IT Ticketing System template", () => { |
||||
|
// Select IT Ticketing System template
|
||||
|
cy.get(".template-thumbnail-text") |
||||
|
.contains(templateName).parentsUntil(".template-grid").within(() => { |
||||
|
cy.get(".spectrum-Button").contains("Use template").click({ force: true }) |
||||
|
}) |
||||
|
|
||||
|
// Confirm URL matches template name
|
||||
|
const appUrl = cy.get(".app-server") |
||||
|
appUrl.invoke('text').then(appUrlText => { |
||||
|
expect(appUrlText).to.equal(`${Cypress.config().baseUrl}/app/` + templateNameParsed) |
||||
|
}) |
||||
|
|
||||
|
// Create App
|
||||
|
cy.get(".spectrum-Dialog-grid").within(() => { |
||||
|
cy.get(".spectrum-Button").contains("Create app").click({ force: true }) |
||||
|
}) |
||||
|
|
||||
|
// Publish App
|
||||
|
cy.wait(2000) // Wait for app to generate
|
||||
|
cy.get(".toprightnav").contains("Publish").click({ force: true }) |
||||
|
cy.get(".spectrum-Dialog-grid").within(() => { |
||||
|
cy.get(".spectrum-Button").contains("Publish").click({ force: true }) |
||||
|
}) |
||||
|
|
||||
|
// Verify Published app
|
||||
|
cy.wait(2000) // Wait for App to publish and modal to appear
|
||||
|
cy.get(".spectrum-Dialog-grid").within(() => { |
||||
|
cy.get(".spectrum-Button").contains("View App").click({ force: true }) |
||||
|
cy.window().its('open').should('be.calledOnce') |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
xit("should filter tickets by status", () => { |
||||
|
// Visit published app
|
||||
|
cy.visit(`${Cypress.config().baseUrl}/app/` + templateNameParsed) |
||||
|
cy.wait(1000) |
||||
|
|
||||
|
// Tickets section
|
||||
|
cy.get(".links").contains("Tickets").click({ force: true }) |
||||
|
cy.wait(1000) |
||||
|
|
||||
|
// Filter by stage - Confirm table updates
|
||||
|
cy.get(".spectrum-Picker").contains("Filter by status").click({ force: true }) |
||||
|
cy.get(".spectrum-Menu").find('li').its('length').then(len => { |
||||
|
for (let i = 1; i < len; i++) { |
||||
|
cy.get(".spectrum-Menu-item").eq(i).click() |
||||
|
const stage = cy.get(".spectrum-Picker-label") |
||||
|
stage.invoke('text').then(stageText => { |
||||
|
if (stageText == "In progress" || stageText == "On hold" || stageText == "Triaged") { |
||||
|
cy.get(".placeholder").should('contain', 'No rows found') |
||||
|
} |
||||
|
else { |
||||
|
cy.get(".spectrum-Table-row").should('contain', stageText) |
||||
|
} |
||||
|
cy.get(".spectrum-Picker").contains(stageText).click({ force: true }) |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
}) |
||||
|
}) |
||||
|
}) |
||||
@ -0,0 +1,26 @@ |
|||||
|
// createApp test
|
||||
|
export const CREATE_APP_BUTTON = '[data-cy="create-app-btn"]' |
||||
|
export const TEMPLATE_CATEGORY_FILTER = ".template-category-filters" |
||||
|
export const TEMPLATE_CATEGORY = ".template-categories" |
||||
|
export const APP_TABLE = ".appTable" |
||||
|
export const SPECTRUM_BUTTON_TEMPLATE = ".spectrum-Button" |
||||
|
export const TEMPLATE_CATEGORY_ACTIONGROUP = ".template-category" |
||||
|
export const TEMPLATE_CATEGORY_FILTER_ACTIONBUTTON = |
||||
|
".template-category-filters .spectrum-ActionButton" |
||||
|
export const SPECTRUM_MODAL = ".spectrum-Modal" |
||||
|
export const APP_NAME_INPUT = "input" // we need to update this with atribute cy-data;
|
||||
|
export const SPECTRUM_BUTTON_GROUP = ".spectrum-ButtonGroup" |
||||
|
export const SPECTRUM_MODAL_INPUT = ".spectrum-Modal input" |
||||
|
|
||||
|
//AddMultiOptionDatatype test
|
||||
|
export const CATEGORY_DATA = '[data-cy="category-Data"]' |
||||
|
export const COMPONENT_DATA_PROVIDER = '[data-cy="component-Data Provider"]' |
||||
|
export const DATASOURCE_PROP_CONTROL = '[data-cy="dataSource-prop-control"]' |
||||
|
export const DROPDOWN = ".dropdown" |
||||
|
export const SPECTRUM_Picker_LABEL = ".spectrum-Picker-label" |
||||
|
export const DATASOURCE_FIELD_CONTROL = '[data-cy="field-prop-control"]' |
||||
|
export const OPTION_TYPE_PROP_CONTROL = '[data-cy="optionsType-prop-control' |
||||
|
|
||||
|
//AddRadioButtons
|
||||
|
export const SPECTRUM_POPOVER = ".spectrum-Popover" |
||||
|
export const OPTION_SOURCE_PROP_CONROL = '[data-cy="optionsSource-prop-control' |
||||
@ -0,0 +1,10 @@ |
|||||
|
{ |
||||
|
"reporterEnabled": "mochawesome", |
||||
|
"mochawesomeReporterOptions": { |
||||
|
"reportDir": "cypress/reports", |
||||
|
"quiet": true, |
||||
|
"overwrite": false, |
||||
|
"html": false, |
||||
|
"json": true |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,130 @@ |
|||||
|
#!/usr/bin/env node
|
||||
|
|
||||
|
const fetch = require("node-fetch") |
||||
|
const path = require("path") |
||||
|
const fs = require("fs") |
||||
|
|
||||
|
const WEBHOOK_URL = process.env.CYPRESS_WEBHOOK_URL |
||||
|
const OUTCOME = process.env.CYPRESS_OUTCOME |
||||
|
const DASHBOARD_URL = process.env.CYPRESS_DASHBOARD_URL |
||||
|
const GIT_SHA = process.env.GITHUB_SHA |
||||
|
const GITHUB_ACTIONS_RUN_URL = process.env.GITHUB_ACTIONS_RUN_URL |
||||
|
|
||||
|
async function generateReport() { |
||||
|
// read the report file
|
||||
|
const REPORT_PATH = path.resolve( |
||||
|
__dirname, |
||||
|
"..", |
||||
|
"cypress", |
||||
|
"reports", |
||||
|
"testReport.json" |
||||
|
) |
||||
|
const report = fs.readFileSync(REPORT_PATH, "utf-8") |
||||
|
return JSON.parse(report) |
||||
|
} |
||||
|
|
||||
|
async function discordCypressResultsNotification(report) { |
||||
|
const { |
||||
|
suites, |
||||
|
tests, |
||||
|
passes, |
||||
|
pending, |
||||
|
failures, |
||||
|
duration, |
||||
|
passPercent, |
||||
|
skipped, |
||||
|
} = report.stats |
||||
|
|
||||
|
const options = { |
||||
|
method: "POST", |
||||
|
headers: { |
||||
|
"Content-Type": "application/json", |
||||
|
Accept: "application/json", |
||||
|
}, |
||||
|
body: JSON.stringify({ |
||||
|
content: `**Nightly Tests Status**: ${OUTCOME}`, |
||||
|
embeds: [ |
||||
|
{ |
||||
|
title: "Budi QA Bot", |
||||
|
description: `Nightly Tests`, |
||||
|
url: GITHUB_ACTIONS_RUN_URL, |
||||
|
color: OUTCOME === "success" ? 3066993 : 15548997, |
||||
|
timestamp: new Date(), |
||||
|
footer: { |
||||
|
icon_url: "http://bbui.budibase.com/budibase-logo.png", |
||||
|
text: "Budibase QA Bot", |
||||
|
}, |
||||
|
thumbnail: { |
||||
|
url: "http://bbui.budibase.com/budibase-logo.png", |
||||
|
}, |
||||
|
author: { |
||||
|
name: "Budibase QA Bot", |
||||
|
url: "https://discordapp.com", |
||||
|
icon_url: "http://bbui.budibase.com/budibase-logo.png", |
||||
|
}, |
||||
|
fields: [ |
||||
|
{ |
||||
|
name: "Commit", |
||||
|
value: `https://github.com/Budibase/budibase/commit/${GIT_SHA}`, |
||||
|
}, |
||||
|
{ |
||||
|
name: "Cypress Dashboard URL", |
||||
|
value: DASHBOARD_URL || "None Supplied", |
||||
|
}, |
||||
|
{ |
||||
|
name: "Github Actions Run URL", |
||||
|
value: GITHUB_ACTIONS_RUN_URL || "None Supplied", |
||||
|
}, |
||||
|
{ |
||||
|
name: "Test Suites", |
||||
|
value: suites, |
||||
|
}, |
||||
|
{ |
||||
|
name: "Tests", |
||||
|
value: tests, |
||||
|
}, |
||||
|
{ |
||||
|
name: "Passed", |
||||
|
value: passes, |
||||
|
}, |
||||
|
{ |
||||
|
name: "Pending", |
||||
|
value: pending, |
||||
|
}, |
||||
|
{ |
||||
|
name: "Skipped", |
||||
|
value: skipped, |
||||
|
}, |
||||
|
{ |
||||
|
name: "Failures", |
||||
|
value: failures, |
||||
|
}, |
||||
|
{ |
||||
|
name: "Duration", |
||||
|
value: `${duration / 1000} Seconds`, |
||||
|
}, |
||||
|
{ |
||||
|
name: "Pass Percentage", |
||||
|
value: Math.floor(passPercent), |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
], |
||||
|
}), |
||||
|
} |
||||
|
const response = await fetch(WEBHOOK_URL, options) |
||||
|
|
||||
|
if (response.status >= 400) { |
||||
|
const text = await response.text() |
||||
|
console.error( |
||||
|
`Error sending discord webhook. \nStatus: ${response.status}. \nResponse Body: ${text}. \nRequest Body: ${options.body}` |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async function run() { |
||||
|
const report = await generateReport() |
||||
|
await discordCypressResultsNotification(report) |
||||
|
} |
||||
|
|
||||
|
run() |
||||
@ -0,0 +1,56 @@ |
|||||
|
<script> |
||||
|
import { Body, ProgressBar, Label } from "@budibase/bbui" |
||||
|
import { onMount } from "svelte" |
||||
|
export let usage |
||||
|
|
||||
|
let percentage |
||||
|
let unlimited = false |
||||
|
|
||||
|
const isUnlimited = () => { |
||||
|
if (usage.total === -1) { |
||||
|
return true |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
const getPercentage = () => { |
||||
|
return Math.min(Math.ceil((usage.used / usage.total) * 100), 100) |
||||
|
} |
||||
|
|
||||
|
onMount(() => { |
||||
|
unlimited = isUnlimited() |
||||
|
percentage = getPercentage() |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
<div class="usage"> |
||||
|
<div class="info"> |
||||
|
<Label size="XL">{usage.name}</Label> |
||||
|
{#if unlimited} |
||||
|
<Body size="S">{usage.used}</Body> |
||||
|
{:else} |
||||
|
<Body size="S">{usage.used} / {usage.total}</Body> |
||||
|
{/if} |
||||
|
</div> |
||||
|
<div> |
||||
|
{#if unlimited} |
||||
|
<Body size="S">Unlimited</Body> |
||||
|
{:else} |
||||
|
<ProgressBar width={"100%"} duration={1} value={percentage} /> |
||||
|
{/if} |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.usage { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
justify-content: space-between; |
||||
|
} |
||||
|
.info { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
justify-content: space-between; |
||||
|
gap: var(--spacing-m); |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,139 @@ |
|||||
|
<script> |
||||
|
import { |
||||
|
Body, |
||||
|
Divider, |
||||
|
Heading, |
||||
|
Layout, |
||||
|
notifications, |
||||
|
Link, |
||||
|
} from "@budibase/bbui" |
||||
|
import { onMount } from "svelte" |
||||
|
import { admin, auth, licensing } from "stores/portal" |
||||
|
import Usage from "components/usage/Usage.svelte" |
||||
|
|
||||
|
let staticUsage = [] |
||||
|
let monthlyUsage = [] |
||||
|
let loaded = false |
||||
|
|
||||
|
$: quotaUsage = $licensing.quotaUsage |
||||
|
$: license = $auth.user?.license |
||||
|
|
||||
|
const upgradeUrl = `${$admin.accountPortalUrl}/portal/upgrade` |
||||
|
|
||||
|
const setMonthlyUsage = () => { |
||||
|
monthlyUsage = [] |
||||
|
if (quotaUsage.monthly) { |
||||
|
for (let [key, value] of Object.entries(license.quotas.usage.monthly)) { |
||||
|
const used = quotaUsage.monthly.current[key] |
||||
|
if (used !== undefined) { |
||||
|
monthlyUsage.push({ |
||||
|
name: value.name, |
||||
|
used: used, |
||||
|
total: value.value, |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const setStaticUsage = () => { |
||||
|
staticUsage = [] |
||||
|
for (let [key, value] of Object.entries(license.quotas.usage.static)) { |
||||
|
const used = quotaUsage.usageQuota[key] |
||||
|
if (used !== undefined) { |
||||
|
staticUsage.push({ |
||||
|
name: value.name, |
||||
|
used: used, |
||||
|
total: value.value, |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const capitalise = string => { |
||||
|
if (string) { |
||||
|
return string.charAt(0).toUpperCase() + string.slice(1) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const init = async () => { |
||||
|
try { |
||||
|
await licensing.getQuotaUsage() |
||||
|
} catch (e) { |
||||
|
console.error(e) |
||||
|
notifications.error(e) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
onMount(async () => { |
||||
|
await init() |
||||
|
loaded = true |
||||
|
}) |
||||
|
|
||||
|
$: { |
||||
|
if (license && quotaUsage) { |
||||
|
setMonthlyUsage() |
||||
|
setStaticUsage() |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
{#if loaded} |
||||
|
<Layout> |
||||
|
<Heading>Usage</Heading> |
||||
|
<Body |
||||
|
>Get information about your current usage within Budibase. |
||||
|
{#if $admin.cloud} |
||||
|
{#if $auth.user?.accountPortalAccess} |
||||
|
To upgrade your plan and usage limits visit your <Link |
||||
|
size="L" |
||||
|
href={upgradeUrl}>Account</Link |
||||
|
>. |
||||
|
{:else} |
||||
|
Contact your account holder to upgrade your usage limits. |
||||
|
{/if} |
||||
|
{/if} |
||||
|
</Body> |
||||
|
</Layout> |
||||
|
<Layout gap="S"> |
||||
|
<Divider size="S" /> |
||||
|
</Layout> |
||||
|
<Layout gap="S" noPadding> |
||||
|
<Layout gap="XS"> |
||||
|
<Body size="S">YOUR PLAN</Body> |
||||
|
<Heading size="S">{capitalise(license?.plan.type)}</Heading> |
||||
|
</Layout> |
||||
|
<Layout gap="S"> |
||||
|
<Body size="S">USAGE</Body> |
||||
|
<div class="usages"> |
||||
|
{#each staticUsage as usage} |
||||
|
<div class="usage"> |
||||
|
<Usage {usage} /> |
||||
|
</div> |
||||
|
{/each} |
||||
|
</div> |
||||
|
</Layout> |
||||
|
{#if monthlyUsage.length} |
||||
|
<Layout gap="S"> |
||||
|
<Body size="S">MONTHLY</Body> |
||||
|
<div class="usages"> |
||||
|
{#each monthlyUsage as usage} |
||||
|
<div class="usage"> |
||||
|
<Usage {usage} /> |
||||
|
</div> |
||||
|
{/each} |
||||
|
</div> |
||||
|
</Layout> |
||||
|
<div /> |
||||
|
{/if} |
||||
|
</Layout> |
||||
|
{/if} |
||||
|
|
||||
|
<style> |
||||
|
.usages { |
||||
|
display: grid; |
||||
|
column-gap: 60px; |
||||
|
row-gap: 50px; |
||||
|
grid-template-columns: 1fr 1fr 1fr; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,29 @@ |
|||||
|
import { writable } from "svelte/store" |
||||
|
import { API } from "api" |
||||
|
|
||||
|
export const createLicensingStore = () => { |
||||
|
const DEFAULT = { |
||||
|
plans: {}, |
||||
|
} |
||||
|
|
||||
|
const store = writable(DEFAULT) |
||||
|
|
||||
|
const actions = { |
||||
|
getQuotaUsage: async () => { |
||||
|
const quotaUsage = await API.getQuotaUsage() |
||||
|
store.update(state => { |
||||
|
return { |
||||
|
...state, |
||||
|
quotaUsage, |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
subscribe: store.subscribe, |
||||
|
...actions, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const licensing = createLicensingStore() |
||||
@ -0,0 +1,36 @@ |
|||||
|
module FirebaseMock { |
||||
|
const firebase: any = {} |
||||
|
|
||||
|
firebase.Firestore = function () { |
||||
|
this.get = jest.fn(() => [ |
||||
|
{ |
||||
|
data: jest.fn(() => ({ result: "test" })), |
||||
|
}, |
||||
|
]) |
||||
|
|
||||
|
this.update = jest.fn() |
||||
|
this.set = jest.fn() |
||||
|
this.delete = jest.fn() |
||||
|
|
||||
|
this.doc = jest.fn(() => ({ |
||||
|
update: this.update, |
||||
|
set: this.set, |
||||
|
delete: this.delete, |
||||
|
get: jest.fn(() => ({ |
||||
|
data: jest.fn(() => ({ result: "test" })), |
||||
|
})), |
||||
|
id: "test_id", |
||||
|
})) |
||||
|
|
||||
|
this.where = jest.fn(() => ({ |
||||
|
get: this.get, |
||||
|
})) |
||||
|
|
||||
|
this.collection = jest.fn(() => ({ |
||||
|
doc: this.doc, |
||||
|
where: this.where, |
||||
|
})) |
||||
|
} |
||||
|
|
||||
|
module.exports = firebase |
||||
|
} |
||||
@ -1,24 +0,0 @@ |
|||||
const { analytics } = require("@budibase/backend-core") |
|
||||
|
|
||||
exports.isEnabled = async ctx => { |
|
||||
ctx.body = { |
|
||||
enabled: analytics.enabled(), |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
exports.endUserPing = async ctx => { |
|
||||
if (!analytics.enabled()) { |
|
||||
ctx.body = { |
|
||||
ping: false, |
|
||||
} |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
analytics.captureEvent(ctx.user._id, "budibase:end_user_ping", { |
|
||||
appId: ctx.appId, |
|
||||
}) |
|
||||
|
|
||||
ctx.body = { |
|
||||
ping: true, |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,7 @@ |
|||||
|
import { events } from "@budibase/backend-core" |
||||
|
|
||||
|
export const isEnabled = async (ctx: any) => { |
||||
|
ctx.body = { |
||||
|
enabled: events.analyticsEnabled(), |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue