Skip to content

Commit 183cd6e

Browse files
authored
feat(tesseract): Support Funnels extension (cube-js#11187)
1 parent 6e5c98f commit 183cd6e

13 files changed

Lines changed: 107 additions & 4 deletions

File tree

packages/cubejs-schema-compiler/src/adapter/BaseQuery.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -967,7 +967,9 @@ export class BaseQuery {
967967
};
968968

969969
try {
970-
const buildResult = nativeBuildSqlAndParams(queryParams);
970+
// Establish the current query context, JS extensions like Funnels/RefreshKeys can resolve compiler.contextQuery()
971+
// during the member-SQL callbacks the native planner makes back into JS.
972+
const buildResult = this.compilers.compiler.withQuery(this, () => nativeBuildSqlAndParams(queryParams));
971973

972974
const [query, params, preAggResult] = buildResult;
973975
const paramsArray = [...params];
@@ -4037,7 +4039,7 @@ export class BaseQuery {
40374039
}
40384040

40394041
inDbTimeZone(date) {
4040-
return localTimestampToUtc(this.timezone, this.timestampFormat(), date);
4042+
return localTimestampToUtc(this.timezone || 'UTC', this.timestampFormat(), date);
40414043
}
40424044

40434045
/**
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { PostgresQuery } from '../../../src/adapter/PostgresQuery';
2+
import { prepareJsCompiler } from '../../unit/PrepareCompiler';
3+
import { dbRunner } from './PostgresDBRunner';
4+
5+
describe('Extensions', () => {
6+
jest.setTimeout(200 * 1000);
7+
8+
// A three-step funnel (View -> Cart -> Purchase). Event sources are inline
9+
// `VALUES` selects so the test is fully self-contained and needs no fixture.
10+
//
11+
// Raw events (user_id, ts):
12+
// View: 1@01-01, 2@01-02, 3@01-03, 4@01-04
13+
// Cart: 1@01-03, 2@01-20, 3@2016-12-31, 4@01-06 (timeToConvert: 5 day)
14+
// Purchase: 1@01-10
15+
//
16+
// Cart has `timeToConvert: '5 day'`, so a Cart event only counts when it
17+
// happens after the View event and within 5 days of it:
18+
// user 1: cart 01-03 is 2 days after view 01-01 -> converts
19+
// user 2: cart 01-20 is 18 days after view 01-02 -> dropped (too late)
20+
// user 3: cart 2016-12-31 is before view 01-03 -> dropped (before)
21+
// user 4: cart 01-06 is 2 days after view 01-04 -> converts
22+
// Purchase joins off Cart, so only users who reached Cart can reach it:
23+
// user 1 purchases, user 4 does not.
24+
//
25+
// Expected conversions per step: View=4, Cart=2, Purchase=1.
26+
const { compiler, joinGraph, cubeEvaluator } = prepareJsCompiler(`
27+
const Funnels = require('Funnels');
28+
29+
cube('Funnel', {
30+
extends: Funnels.eventFunnel({
31+
userId: { sql: 'user_id' },
32+
time: { sql: 'ts' },
33+
steps: [
34+
{
35+
name: 'View',
36+
eventsTable: {
37+
sql: "select user_id, ts from (values (1, timestamp '2017-01-01'), (2, timestamp '2017-01-02'), (3, timestamp '2017-01-03'), (4, timestamp '2017-01-04')) as t(user_id, ts)"
38+
}
39+
},
40+
{
41+
name: 'Cart',
42+
timeToConvert: '5 day',
43+
eventsTable: {
44+
sql: "select user_id, ts from (values (1, timestamp '2017-01-03'), (2, timestamp '2017-01-20'), (3, timestamp '2016-12-31'), (4, timestamp '2017-01-06')) as t(user_id, ts)"
45+
}
46+
},
47+
{
48+
name: 'Purchase',
49+
eventsTable: {
50+
sql: "select user_id, ts from (values (1, timestamp '2017-01-10')) as t(user_id, ts)"
51+
}
52+
}
53+
]
54+
})
55+
})
56+
`, { allowNodeRequire: true });
57+
58+
async function runQueryTest(q: any, expectedResult: any) {
59+
await compiler.compile();
60+
const query = new PostgresQuery(
61+
{ joinGraph, cubeEvaluator, compiler },
62+
{ ...q, timezone: 'UTC', preAggregationsSchema: '' }
63+
);
64+
65+
const qp = query.buildSqlAndParams();
66+
console.log(qp);
67+
68+
const res = await dbRunner.testQuery(qp);
69+
console.log(JSON.stringify(res));
70+
71+
expect(res).toEqual(expectedResult);
72+
}
73+
74+
describe('Funnels', () => {
75+
it('conversions per step', async () => runQueryTest({
76+
measures: ['Funnel.conversions'],
77+
dimensions: ['Funnel.step'],
78+
order: [{ id: 'Funnel.conversions', desc: true }],
79+
}, [
80+
{ funnel__step: 'View', funnel__conversions: '4' },
81+
{ funnel__step: 'Cart', funnel__conversions: '2' },
82+
{ funnel__step: 'Purchase', funnel__conversions: '1' },
83+
]));
84+
85+
it('total conversions across all steps', async () => runQueryTest({
86+
measures: ['Funnel.conversions'],
87+
}, [
88+
{ funnel__conversions: '7' },
89+
]));
90+
});
91+
});

packages/cubejs-schema-compiler/test/unit/extensions.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ describe('Extensions', () => {
6868
timeDimensions: [{
6969
dimension: 'VisitorsFunnel.time',
7070
granularity: 'day',
71-
dateRange: { from: '2017-01-01', to: '2017-01-30' }
71+
dateRange: ['2017-01-01', '2017-01-30']
7272
}],
7373
timezone: 'America/Los_Angeles'
7474
});
@@ -92,7 +92,7 @@ describe('Extensions', () => {
9292
timeDimensions: [{
9393
dimension: 'FooBar.time',
9494
granularity: 'day',
95-
dateRange: { from: '2017-01-01', to: '2017-01-30' }
95+
dateRange: ['2017-01-01', '2017-01-30']
9696
}],
9797
timezone: 'America/Los_Angeles'
9898
});

packages/cubejs-testing-drivers/fixtures/athena.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"CUBEJS_SQL_PASSWORD": "admin_password",
1818
"CUBESQL_SQL_PUSH_DOWN": "true",
1919
"CUBEJS_TESSERACT_SQL_PLANNER": "${DRIVERS_TESTS_CUBEJS_TESSERACT_SQL_PLANNER}",
20+
"CUBEJS_TESSERACT_PRE_AGGREGATIONS": "${DRIVERS_TESTS_CUBEJS_TESSERACT_SQL_PLANNER}",
2021
"CUBEJS_TRANSPILATION_NATIVE": "${DRIVERS_TESTS_CUBEJS_TRANSPILATION_NATIVE}"
2122
},
2223
"ports" : ["4000", "5656"]

packages/cubejs-testing-drivers/fixtures/bigquery.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"CUBEJS_DB_EXPORT_BUCKET": "cube-open-source-export-bucket",
2020
"CUBEJS_DB_EXPORT_BUCKET_TYPE": "gcp",
2121
"CUBEJS_TESSERACT_SQL_PLANNER": "${DRIVERS_TESTS_CUBEJS_TESSERACT_SQL_PLANNER}",
22+
"CUBEJS_TESSERACT_PRE_AGGREGATIONS": "${DRIVERS_TESTS_CUBEJS_TESSERACT_SQL_PLANNER}",
2223
"CUBEJS_TRANSPILATION_NATIVE": "${DRIVERS_TESTS_CUBEJS_TRANSPILATION_NATIVE}"
2324
},
2425
"ports" : ["4000", "5656"]

packages/cubejs-testing-drivers/fixtures/clickhouse.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"CUBEJS_SQL_PASSWORD": "admin_password",
3939
"CUBESQL_SQL_PUSH_DOWN": "true",
4040
"CUBEJS_TESSERACT_SQL_PLANNER": "${DRIVERS_TESTS_CUBEJS_TESSERACT_SQL_PLANNER}",
41+
"CUBEJS_TESSERACT_PRE_AGGREGATIONS": "${DRIVERS_TESTS_CUBEJS_TESSERACT_SQL_PLANNER}",
4142
"CUBEJS_TRANSPILATION_NATIVE": "${DRIVERS_TESTS_CUBEJS_TRANSPILATION_NATIVE}"
4243
},
4344
"depends_on": ["data"],

packages/cubejs-testing-drivers/fixtures/databricks-jdbc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"CUBEJS_SQL_PASSWORD": "admin_password",
8080
"CUBESQL_SQL_PUSH_DOWN": "true",
8181
"CUBEJS_TESSERACT_SQL_PLANNER": "${DRIVERS_TESTS_CUBEJS_TESSERACT_SQL_PLANNER}",
82+
"CUBEJS_TESSERACT_PRE_AGGREGATIONS": "${DRIVERS_TESTS_CUBEJS_TESSERACT_SQL_PLANNER}",
8283
"CUBEJS_TRANSPILATION_NATIVE": "${DRIVERS_TESTS_CUBEJS_TRANSPILATION_NATIVE}"
8384
},
8485
"ports" : ["4000", "5656"]

packages/cubejs-testing-drivers/fixtures/mssql.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"CUBEJS_SQL_PASSWORD": "admin_password",
1414
"CUBESQL_SQL_PUSH_DOWN": "true",
1515
"CUBEJS_TESSERACT_SQL_PLANNER": "${DRIVERS_TESTS_CUBEJS_TESSERACT_SQL_PLANNER}",
16+
"CUBEJS_TESSERACT_PRE_AGGREGATIONS": "${DRIVERS_TESTS_CUBEJS_TESSERACT_SQL_PLANNER}",
1617
"CUBEJS_TRANSPILATION_NATIVE": "${DRIVERS_TESTS_CUBEJS_TRANSPILATION_NATIVE}"
1718
},
1819
"depends_on": ["data"],

packages/cubejs-testing-drivers/fixtures/mysql.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"CUBEJS_SQL_PASSWORD": "admin_password",
1515
"CUBESQL_SQL_PUSH_DOWN": "true",
1616
"CUBEJS_TESSERACT_SQL_PLANNER": "${DRIVERS_TESTS_CUBEJS_TESSERACT_SQL_PLANNER}",
17+
"CUBEJS_TESSERACT_PRE_AGGREGATIONS": "${DRIVERS_TESTS_CUBEJS_TESSERACT_SQL_PLANNER}",
1718
"CUBEJS_TRANSPILATION_NATIVE": "${DRIVERS_TESTS_CUBEJS_TRANSPILATION_NATIVE}"
1819
},
1920
"depends_on": ["data"],

packages/cubejs-testing-drivers/fixtures/oracle.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"CUBEJS_SQL_PASSWORD": "admin_password",
1616
"CUBESQL_SQL_PUSH_DOWN": "true",
1717
"CUBEJS_TESSERACT_SQL_PLANNER": "${DRIVERS_TESTS_CUBEJS_TESSERACT_SQL_PLANNER}",
18+
"CUBEJS_TESSERACT_PRE_AGGREGATIONS": "${DRIVERS_TESTS_CUBEJS_TESSERACT_SQL_PLANNER}",
1819
"CUBEJS_TRANSPILATION_NATIVE": "${DRIVERS_TESTS_CUBEJS_TRANSPILATION_NATIVE}"
1920
},
2021
"depends_on": ["data"],

0 commit comments

Comments
 (0)