Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import R from 'ramda';

import type { NodePath } from '@babel/traverse';
import {
SymbolResolver,
TranspilerCubeResolver,
TranspilerInterface,
TranspilerSymbolResolver,
Expand Down Expand Up @@ -59,7 +60,7 @@ export class CubePropContextTranspiler implements TranspilerInterface {
args[0].node.type === 'TemplateLiteral' &&
args[0].node.quasis.length &&
args[0].node.quasis[0].value.cooked;
args[args.length - 1].traverse(this.sqlAndReferencesFieldVisitor(cubeName));
args[args.length - 1].traverse(this.sqlAndReferencesFieldVisitor(cubeName as string));
args[args.length - 1].traverse(
this.knownIdentifiersInjectVisitor('extends', name => this.cubeDictionary.resolveCube(name))
);
Expand All @@ -72,15 +73,16 @@ export class CubePropContextTranspiler implements TranspilerInterface {
};
}

protected transformObjectProperty(path: NodePath<t.ObjectProperty>, resolveSymbol: (name: string) => void) {
protected transformObjectProperty(path: NodePath<t.ObjectProperty>, resolveSymbol: SymbolResolver) {
CubePropContextTranspiler.replaceValueWithArrowFunction(resolveSymbol, path.get('value'));
}

public static replaceValueWithArrowFunction(resolveSymbol: (name: string) => any, value: NodePath<any>) {
const knownIds = CubePropContextTranspiler.collectKnownIdentifiers(
public static replaceValueWithArrowFunction(resolveSymbol: SymbolResolver, value: NodePath<any>) {
const knownIds = CubePropContextTranspiler.collectKnownIdentifiersAndTransform(
resolveSymbol,
value,
);

value.replaceWith(
t.arrowFunctionExpression(
knownIds.map(i => t.identifier(i)),
Expand All @@ -91,7 +93,7 @@ export class CubePropContextTranspiler implements TranspilerInterface {
);
}

protected sqlAndReferencesFieldVisitor(cubeName): TraverseObject {
protected sqlAndReferencesFieldVisitor(cubeName: string | null | undefined): TraverseObject {
const resolveSymbol = n => this.viewCompiler.resolveSymbol(cubeName, n) ||
this.cubeSymbols.resolveSymbol(cubeName, n) ||
this.cubeSymbols.isCurrentCube(n);
Expand Down Expand Up @@ -171,7 +173,7 @@ export class CubePropContextTranspiler implements TranspilerInterface {
return fp;
}

protected knownIdentifiersInjectVisitor(field: RegExp | string, resolveSymbol: (name: string) => void): TraverseObject {
protected knownIdentifiersInjectVisitor(field: RegExp | string, resolveSymbol: SymbolResolver): TraverseObject {
return {
ObjectProperty: (path) => {
if (path.node.key.type === 'Identifier' && path.node.key.name.match(field)) {
Expand All @@ -181,8 +183,8 @@ export class CubePropContextTranspiler implements TranspilerInterface {
};
}

protected static collectKnownIdentifiers(resolveSymbol, path: NodePath) {
const identifiers = [];
protected static collectKnownIdentifiers(resolveSymbol: SymbolResolver, path: NodePath): string[] {
const identifiers: string[] = [];

if (path.node.type === 'Identifier') {
CubePropContextTranspiler.matchAndPushIdentifier(path, resolveSymbol, identifiers);
Expand All @@ -197,7 +199,26 @@ export class CubePropContextTranspiler implements TranspilerInterface {
return R.uniq(identifiers);
}

protected static matchAndPushIdentifier(path, resolveSymbol, identifiers) {
protected static collectKnownIdentifiersAndTransform(resolveSymbol: SymbolResolver, path: NodePath): string[] {
const identifiers: string[] = [];

if (path.node.type === 'Identifier') {
CubePropContextTranspiler.matchAndPushIdentifier(path, resolveSymbol, identifiers);
}

path.traverse({
Identifier: (p) => {
CubePropContextTranspiler.matchAndTransformIdentifier(p, resolveSymbol, identifiers);
},
MemberExpression: (p) => {
CubePropContextTranspiler.transformUserAttributesMemberExpression(p);
}
});

return R.uniq(identifiers);
}

protected static matchAndPushIdentifier(path, resolveSymbol: SymbolResolver, identifiers: string[]) {
if (
(!path.parent ||
(path.parent.type !== 'MemberExpression' || path.parent.type === 'MemberExpression' && path.key !== 'property')
Expand All @@ -207,4 +228,33 @@ export class CubePropContextTranspiler implements TranspilerInterface {
identifiers.push(path.node.name);
}
}

protected static matchAndTransformIdentifier(path, resolveSymbol: SymbolResolver, identifiers: string[]) {
if (
(!path.parent ||
(path.parent.type !== 'MemberExpression' || path.parent.type === 'MemberExpression' && path.key !== 'property')
) &&
resolveSymbol(path.node.name)
) {
// Special handling for userAttributes - replace in parameter list with securityContext
if (path.node.name === 'userAttributes') {
identifiers.push('securityContext');
} else {
identifiers.push(path.node.name);
}
}
}

protected static transformUserAttributesMemberExpression(path: NodePath<t.MemberExpression>) {
// Check if this is userAttributes.someProperty (object should be identifier named 'userAttributes')
if (t.isIdentifier(path.node.object, { name: 'userAttributes' })) {
// Replace userAttributes with securityContext.cubeCloud.userAttributes
const securityContext = t.identifier('securityContext');
const cubeCloud = t.memberExpression(securityContext, t.identifier('cubeCloud'));
const userAttributes = t.memberExpression(cubeCloud, t.identifier('userAttributes'));
const newMemberExpression = t.memberExpression(userAttributes, path.node.property, path.node.computed);

path.replaceWith(newMemberExpression);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ export interface TranspilerSymbolResolver {
export interface TranspilerCubeResolver {
resolveCube(name): boolean;
}

export type SymbolResolver = (name: string) => any;
67 changes: 67 additions & 0 deletions packages/cubejs-schema-compiler/test/unit/transpilers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import babelTraverse from '@babel/traverse';
import { prepareJsCompiler } from './PrepareCompiler';
import { ImportExportTranspiler } from '../../src/compiler/transpilers';
import { ErrorReporter } from '../../src/compiler/ErrorReporter';
import { PostgresQuery } from '../../src';

describe('Transpilers', () => {
it('CubeCheckDuplicatePropTranspiler', async () => {
Expand Down Expand Up @@ -50,6 +51,72 @@ describe('Transpilers', () => {
await compiler.compile();
});

it('CubePropContextTranspiler with full path to userAttributes should work normally', async () => {
const { cubeEvaluator, compiler } = prepareJsCompiler(`
cube(\`Test\`, {
sql: 'SELECT * FROM users',
dimensions: {
userId: {
sql: \`userId\`,
type: 'string'
}
},
accessPolicy: [
{
role: \`*\`,
rowLevel: {
filters: [
{
member: \`userId\`,
operator: \`equals\`,
values: [ securityContext.cubeCloud.userAttributes.userId ]
}
]
}
}
]
})
`);

await compiler.compile();

const transpiledValues = cubeEvaluator.cubeFromPath('Test').accessPolicy?.[0].rowLevel?.filters?.[0].values;
expect(transpiledValues.toString()).toMatch('securityContext.cubeCloud.userAttributes.userId');
});

it('CubePropContextTranspiler with shorthand userAttributes should work normally', async () => {
const { cubeEvaluator, compiler } = prepareJsCompiler(`
cube(\`Test\`, {
sql: 'SELECT * FROM users',
dimensions: {
userId: {
sql: \`userId\`,
type: 'string'
}
},
accessPolicy: [
{
role: \`*\`,
rowLevel: {
filters: [
{
member: \`userId\`,
operator: \`equals\`,
values: [ userAttributes.userId ]
}
]
}
}
]
})
`);

await compiler.compile();

const transpiledValues = cubeEvaluator.cubeFromPath('Test').accessPolicy?.[0].rowLevel?.filters?.[0].values;
expect(transpiledValues.toString()).toMatch('securityContext.cubeCloud.userAttributes.userId');
});

it('ImportExportTranspiler', async () => {
const ieTranspiler = new ImportExportTranspiler();
const errorsReport = new ErrorReporter();
Expand Down
Loading