feat: report editor
commit bfcf271a64c33a60f61f511cec2198d9c8a9c51a Author: Carl-Gerhard Lindesvärd <lindesvard@gmail.com> Date: Wed Nov 26 12:32:40 2025 +0100 wip commit8cd3b89fa3Author: Carl-Gerhard Lindesvärd <lindesvard@gmail.com> Date: Tue Nov 25 22:33:58 2025 +0100 funnel commit95af86dc44Author: Carl-Gerhard Lindesvärd <lindesvard@gmail.com> Date: Tue Nov 25 22:23:25 2025 +0100 wip commit727a218e6bAuthor: Carl-Gerhard Lindesvärd <lindesvard@gmail.com> Date: Tue Nov 25 10:18:26 2025 +0100 conversion wip commit958ba535d6Author: Carl-Gerhard Lindesvärd <lindesvard@gmail.com> Date: Tue Nov 25 10:18:20 2025 +0100 wip commit3bbeb927ccAuthor: Carl-Gerhard Lindesvärd <lindesvard@gmail.com> Date: Tue Nov 25 09:18:48 2025 +0100 wip commitd99335e2f4Author: Carl-Gerhard Lindesvärd <lindesvard@gmail.com> Date: Mon Nov 24 18:08:10 2025 +0100 wip commit1fa61b1ae9Author: Carl-Gerhard Lindesvärd <lindesvard@gmail.com> Date: Mon Nov 24 15:50:28 2025 +0100 ts commit548747d826Author: Carl-Gerhard Lindesvärd <lindesvard@gmail.com> Date: Mon Nov 24 13:17:01 2025 +0100 fix typecheck events -> series commit7b18544085Author: Carl-Gerhard Lindesvärd <lindesvard@gmail.com> Date: Mon Nov 24 13:06:46 2025 +0100 fix report table commit57697a5a39Author: Carl-Gerhard Lindesvärd <lindesvard@gmail.com> Date: Sat Nov 22 00:05:13 2025 +0100 wip commit06fb6c4f3cAuthor: Carl-Gerhard Lindesvärd <lindesvard@gmail.com> Date: Fri Nov 21 11:21:17 2025 +0100 wip commitdd71fd4e11Author: Carl-Gerhard Lindesvärd <lindesvard@gmail.com> Date: Thu Nov 20 13:56:58 2025 +0100 formulas
This commit is contained in:
@@ -155,7 +155,8 @@ export function getChartSql({
|
||||
}
|
||||
|
||||
breakdowns.forEach((breakdown, index) => {
|
||||
const key = `label_${index}`;
|
||||
// Breakdowns start at label_1 (label_0 is reserved for event name)
|
||||
const key = `label_${index + 1}`;
|
||||
sb.select[key] = `${getSelectPropertyKey(breakdown.name)} as ${key}`;
|
||||
sb.groupBy[key] = `${key}`;
|
||||
});
|
||||
@@ -175,8 +176,8 @@ export function getChartSql({
|
||||
|
||||
if (event.segment === 'property_sum' && event.property) {
|
||||
if (event.property === 'revenue') {
|
||||
sb.select.count = `sum(revenue) as count`;
|
||||
sb.where.property = `revenue > 0`;
|
||||
sb.select.count = 'sum(revenue) as count';
|
||||
sb.where.property = 'revenue > 0';
|
||||
} else {
|
||||
sb.select.count = `sum(toFloat64(${getSelectPropertyKey(event.property)})) as count`;
|
||||
sb.where.property = `${getSelectPropertyKey(event.property)} IS NOT NULL AND notEmpty(${getSelectPropertyKey(event.property)})`;
|
||||
@@ -185,8 +186,8 @@ export function getChartSql({
|
||||
|
||||
if (event.segment === 'property_average' && event.property) {
|
||||
if (event.property === 'revenue') {
|
||||
sb.select.count = `avg(revenue) as count`;
|
||||
sb.where.property = `revenue > 0`;
|
||||
sb.select.count = 'avg(revenue) as count';
|
||||
sb.where.property = 'revenue > 0';
|
||||
} else {
|
||||
sb.select.count = `avg(toFloat64(${getSelectPropertyKey(event.property)})) as count`;
|
||||
sb.where.property = `${getSelectPropertyKey(event.property)} IS NOT NULL AND notEmpty(${getSelectPropertyKey(event.property)})`;
|
||||
@@ -195,8 +196,8 @@ export function getChartSql({
|
||||
|
||||
if (event.segment === 'property_max' && event.property) {
|
||||
if (event.property === 'revenue') {
|
||||
sb.select.count = `max(revenue) as count`;
|
||||
sb.where.property = `revenue > 0`;
|
||||
sb.select.count = 'max(revenue) as count';
|
||||
sb.where.property = 'revenue > 0';
|
||||
} else {
|
||||
sb.select.count = `max(toFloat64(${getSelectPropertyKey(event.property)})) as count`;
|
||||
sb.where.property = `${getSelectPropertyKey(event.property)} IS NOT NULL AND notEmpty(${getSelectPropertyKey(event.property)})`;
|
||||
@@ -205,8 +206,8 @@ export function getChartSql({
|
||||
|
||||
if (event.segment === 'property_min' && event.property) {
|
||||
if (event.property === 'revenue') {
|
||||
sb.select.count = `min(revenue) as count`;
|
||||
sb.where.property = `revenue > 0`;
|
||||
sb.select.count = 'min(revenue) as count';
|
||||
sb.where.property = 'revenue > 0';
|
||||
} else {
|
||||
sb.select.count = `min(toFloat64(${getSelectPropertyKey(event.property)})) as count`;
|
||||
sb.where.property = `${getSelectPropertyKey(event.property)} IS NOT NULL AND notEmpty(${getSelectPropertyKey(event.property)})`;
|
||||
@@ -230,14 +231,58 @@ export function getChartSql({
|
||||
return sql;
|
||||
}
|
||||
|
||||
// Add total unique count for user segment using a scalar subquery
|
||||
if (event.segment === 'user') {
|
||||
const totalUniqueSubquery = `(
|
||||
SELECT ${sb.select.count}
|
||||
// Build total_count calculation that accounts for breakdowns
|
||||
// When breakdowns exist, we need to calculate total_count per breakdown group
|
||||
if (breakdowns.length > 0) {
|
||||
// Create a subquery that calculates total_count per breakdown group (without date grouping)
|
||||
// Then reference it in the main query via JOIN
|
||||
const breakdownSelects = breakdowns
|
||||
.map((breakdown, index) => {
|
||||
const key = `label_${index + 1}`;
|
||||
const breakdownExpr = getSelectPropertyKey(breakdown.name);
|
||||
return `${breakdownExpr} as ${key}`;
|
||||
})
|
||||
.join(', ');
|
||||
|
||||
// GROUP BY needs to use the actual expressions, not aliases
|
||||
const breakdownGroupByExprs = breakdowns
|
||||
.map((breakdown) => getSelectPropertyKey(breakdown.name))
|
||||
.join(', ');
|
||||
|
||||
// Build the total_count subquery grouped only by breakdowns (no date)
|
||||
// Extract the count expression without the alias (remove "as count")
|
||||
const countExpression = sb.select.count.replace(/\s+as\s+count$/i, '');
|
||||
const totalCountSubquery = `(
|
||||
SELECT
|
||||
${breakdownSelects},
|
||||
${countExpression} as total_count
|
||||
FROM ${sb.from}
|
||||
${getJoins()}
|
||||
${getWhere()}
|
||||
)`;
|
||||
GROUP BY ${breakdownGroupByExprs}
|
||||
) as total_counts`;
|
||||
|
||||
// Join the total_counts subquery to get total_count per breakdown
|
||||
// Match on the breakdown column values
|
||||
const joinConditions = breakdowns
|
||||
.map((_, index) => {
|
||||
const outerKey = `label_${index + 1}`;
|
||||
return `${outerKey} = total_counts.label_${index + 1}`;
|
||||
})
|
||||
.join(' AND ');
|
||||
|
||||
sb.joins.total_counts = `LEFT JOIN ${totalCountSubquery} ON ${joinConditions}`;
|
||||
// Use any() aggregate since total_count is the same for all rows in a breakdown group
|
||||
sb.select.total_unique_count =
|
||||
'any(total_counts.total_count) as total_count';
|
||||
} else {
|
||||
// No breakdowns - use a simple subquery for total count
|
||||
const totalUniqueSubquery = `(
|
||||
SELECT ${sb.select.count}
|
||||
FROM ${sb.from}
|
||||
${getJoins()}
|
||||
${getWhere()}
|
||||
)`;
|
||||
sb.select.total_unique_count = `${totalUniqueSubquery} as total_count`;
|
||||
}
|
||||
|
||||
@@ -509,12 +554,11 @@ export function getChartStartEndDate(
|
||||
}: Pick<IChartInput, 'endDate' | 'startDate' | 'range'>,
|
||||
timezone: string,
|
||||
) {
|
||||
const ranges = getDatesFromRange(range, timezone);
|
||||
|
||||
if (startDate && endDate) {
|
||||
return { startDate: startDate, endDate: endDate };
|
||||
}
|
||||
|
||||
const ranges = getDatesFromRange(range, timezone);
|
||||
if (!startDate && endDate) {
|
||||
return { startDate: ranges.startDate, endDate: endDate };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user