cube.js 提供了比较多的上下问支持,SECRUITY_CONTEXT,COMPILE_CONTEXT,FILTER_PARAMS,SQL_UTILS
但是在使用的时候可能会有好多问题,主要是原因是cube.js 对于编译的cache 以及不同context 的声明周期不一样
SECURITY_CONTEXT 的使用
推荐基于filter 模式使用,因为cube.js 对于schema 的编译是基于独立vm 处理的,而且就没有暴露相关的全局对象
只暴露的,所以不能直接引用,不然会报错的
参考几个定义
const CONTEXT_SYMBOLS = {
USER_CONTEXT: 'securityContext',
SECURITY_CONTEXT: 'securityContext',
FILTER_PARAMS: 'filterParams',
SQL_UTILS: 'sqlUtils'
};
参考compiler 处理
vm.runInNewContext(file.content, {
view: (name, cube) => cubes.push(Object.assign({}, cube, { name, fileName: file.fileName })),
cube:
(name, cube) => (
!cube ?
this.cubeFactory({ ...name, fileName: file.fileName }) :
cubes.push(Object.assign({}, cube, { name, fileName: file.fileName }))
),
context: (name, context) => contexts.push(Object.assign({}, context, { name, fileName: file.fileName })),
dashboardTemplate:
(name, template) => dashboardTemplates.push(Object.assign({}, template, { name, fileName: file.fileName })),
addExport: (obj) => {
exports[file.fileName] = exports[file.fileName] || {};
exports[file.fileName] = Object.assign(exports[file.fileName], obj);
},
setExport: (obj) => {
exports[file.fileName] = obj;
},
asyncModule: (fn) => {
asyncModules.push(fn);
},
require: (extensionName) => {
if (self.extensions[extensionName]) {
return new (self.extensions[extensionName])(this.cubeFactory, self);
} else {
const foundFile = self.resolveModuleFile(file, extensionName, toCompile, errorsReport);
if (!foundFile && this.allowNodeRequire) {
if (extensionName.indexOf('.') === 0) {
extensionName = path.resolve(this.repository.localPath(), extensionName);
}
// eslint-disable-next-line global-require,import/no-dynamic-require
return require(extensionName);
}
self.compileFile(
foundFile,
errorsReport,
cubes,
exports,
contexts,
dashboardTemplates,
toCompile,
compiledFiles
);
exports[foundFile.fileName] = exports[foundFile.fileName] || {};
return exports[foundFile.fileName];
}
},
COMPILE_CONTEXT: R.clone(this.compileContext || {})
}, { filename: file.fileName, timeout: 15000 });
- 参考SECURITY_CONTEXT使用
cube(`Orders`, {
sql: `SELECT * FROM orders WHERE ${SECURITY_CONTEXT.email.filter('email')}`,
dimensions: {
date: {
sql: `date`,
type: `time`
}
}
});
- 一些缺点
因为SECURITY_CONTEXT主要是checkAuth 处理的,如果需要关联扩展需要改动代码
COMPILE_CONTEXT
主要属于schema 编译的时候,这个问题就比较多了,主要场景还是多租户,但是因为cube.js
编译api 的cache 以及COMPILE_CONTEXT执行一次的特性,很多时候容易出现使用问题
比如,有可能COMPILE_CONTEXT的数据不存在,可能会造成undefined的问题
const { securityContext: { total_amount,user_id,bucket } } = COMPILE_CONTEXT;
const filter_count = total_amount
const usercontext = SECURITY_CONTEXT
const user_id2 = JSON.stringify(COMPILE_CONTEXT)
cube(`demoapp`, {
sql: ` SELECT
*
FROM
transactions AS ts
where total_amount > ${filter_count}
`,
measures: {
average_spend_per_customer: {
sql: `${sum}/${count}`,
type: `number`
},
count: {
sql: `total_amount`,
type: `count`,
},
sum: {
sql: `total_amount`,
type: `sum`,
}
},
dimensions: {
total_amount: {
sql: `total_amount`,
type: `number`
},
customer_id: {
sql: `customer_id`,
type: `string`
},
transaction_date: {
sql: `transaction_date`,
type: `time`,
shown: false
},
event_id: {
sql: `event_id`,
type: `string`
}
}
});
FILTER_PARAMS
主要是进行数据过滤的,但是也要注意使用
- 参考格式
FILTER_PARAMS.<CUBE_NAME>.<FILTER_NAME>.filter(expression)
注意filter_name 是必须存在的(就是维度指标)
总结
如果真的需要进行数据动态过滤处理,推荐使用FILTER_PARAMS以及SECURITY_CONTEXT
但是SECURITY_CONTEXT因为数据来源的特性,需要额外注意,一般情况(非多租户场景)不
推荐使用COMPILE_CONTEXT
FILTER_PARAMS 以及SECURITY_CONTEXT 的使用推荐基于filter(不然也会面临编译api cache 的问题)
说明
目前cube.js 还是缺少一些数据源自定义过滤的能力,官方也在计划中,最好的进行过滤还是推荐
使用以上的几个上下文
参考资料
https://cube.dev/docs/cube#context-variables-filter-params