chore:little fixes and formating and linting and patches

This commit is contained in:
2026-03-31 15:50:54 +02:00
parent a1ce71ffb6
commit 9b197abcfa
815 changed files with 22960 additions and 8982 deletions

View File

@@ -1,9 +1,8 @@
import { ifNaN } from '@openpanel/common';
import type { IChartEvent, IReportInput } from '@openpanel/validation';
import { last, reverse, uniq } from 'ramda';
import { last, reverse } from 'ramda';
import sqlstring from 'sqlstring';
import { ch } from '../clickhouse/client';
import { TABLE_NAMES } from '../clickhouse/client';
import { ch, TABLE_NAMES } from '../clickhouse/client';
import { clix } from '../clickhouse/query-builder';
import { createSqlBuilder } from '../sql-builder';
import {
@@ -34,7 +33,10 @@ export class FunnelService {
return group === 'profile_id' ? 'profile_id' : 'session_id';
}
getFunnelConditions(events: IChartEvent[] = [], projectId?: string): string[] {
getFunnelConditions(
events: IChartEvent[] = [],
projectId?: string
): string[] {
return events.map((event) => {
const { sb, getWhere } = createSqlBuilder();
sb.where = getEventFiltersWhereClause(event.filters, projectId);
@@ -92,7 +94,7 @@ export class FunnelService {
.where(
'events.name',
'IN',
eventSeries.map((e) => e.name),
eventSeries.map((e) => e.name)
)
.groupBy([primaryKey, ...additionalGroupBy]);
}
@@ -120,7 +122,7 @@ export class FunnelService {
private fillFunnel(
funnel: { level: number; count: number }[],
steps: number,
steps: number
) {
const filled = Array.from({ length: steps }, (_, index) => {
const level = index + 1;
@@ -146,7 +148,7 @@ export class FunnelService {
toSeries(
funnel: { level: number; count: number; [key: string]: any }[],
breakdowns: { name: string }[] = [],
limit: number | undefined = undefined,
limit: number | undefined = undefined
) {
if (!breakdowns.length) {
return [
@@ -175,7 +177,7 @@ export class FunnelService {
acc[key]!.push({
id: key,
breakdowns: breakdowns.map((b, index) =>
normalizeBreakdownValue(f[`b_${index}`]),
normalizeBreakdownValue(f[`b_${index}`])
),
level: f.level,
count: f.count,
@@ -190,7 +192,7 @@ export class FunnelService {
level: number;
count: number;
}[]
>,
>
);
return Object.values(series);
@@ -200,7 +202,7 @@ export class FunnelService {
return events.flatMap((e) =>
e.filters
?.filter((f) => f.name.startsWith('profile.'))
.map((f) => f.name.replace('profile.', '')),
.map((f) => f.name.replace('profile.', ''))
);
}
@@ -214,7 +216,7 @@ export class FunnelService {
limit,
timezone = 'UTC',
}: IReportInput & { timezone: string; events?: IChartEvent[] }) {
if (!startDate || !endDate) {
if (!(startDate && endDate)) {
throw new Error('startDate and endDate are required');
}
@@ -234,20 +236,20 @@ export class FunnelService {
const profileFilters = this.getProfileFilters(eventSeries);
const anyFilterOnProfile = profileFilters.length > 0;
const anyBreakdownOnProfile = breakdowns.some((b) =>
b.name.startsWith('profile.'),
b.name.startsWith('profile.')
);
const anyFilterOnGroup = eventSeries.some((e) =>
e.filters?.some((f) => f.name.startsWith('group.')),
e.filters?.some((f) => f.name.startsWith('group.'))
);
const anyBreakdownOnGroup = breakdowns.some((b) =>
b.name.startsWith('group.'),
b.name.startsWith('group.')
);
const needsGroupArrayJoin =
anyFilterOnGroup || anyBreakdownOnGroup || funnelGroup === 'group';
// Create the funnel CTE (session-level)
const breakdownSelects = breakdowns.map(
(b, index) => `${getSelectPropertyKey(b.name, projectId)} as b_${index}`,
(b, index) => `${getSelectPropertyKey(b.name, projectId)} as b_${index}`
);
const breakdownGroupBy = breakdowns.map((b, index) => `b_${index}`);
@@ -281,7 +283,7 @@ export class FunnelService {
funnelCte.leftJoin(
`(SELECT ${profileSelectColumns} FROM ${TABLE_NAMES.profiles} FINAL
WHERE project_id = ${sqlstring.escape(projectId)}) as profile`,
'profile.id = events.profile_id',
'profile.id = events.profile_id'
);
}
@@ -296,7 +298,7 @@ export class FunnelService {
if (needsGroupArrayJoin) {
funnelQuery.with(
'_g',
`SELECT id, name, type, properties FROM ${TABLE_NAMES.groups} FINAL WHERE project_id = ${sqlstring.escape(projectId)}`,
`SELECT id, name, type, properties FROM ${TABLE_NAMES.groups} FINAL WHERE project_id = ${sqlstring.escape(projectId)}`
);
}
@@ -304,10 +306,7 @@ export class FunnelService {
// windowFunnel is computed per the primary key (profile_id or session_id),
// so we just filter out level=0 rows — no re-aggregation needed.
funnelQuery.with(
'funnel',
'SELECT * FROM session_funnel WHERE level != 0',
);
funnelQuery.with('funnel', 'SELECT * FROM session_funnel WHERE level != 0');
funnelQuery
.select<{
@@ -331,7 +330,7 @@ export class FunnelService {
const maxLevel = eventSeries.length;
const filledFunnelRes = this.fillFunnel(
data.map((d) => ({ level: d.level, count: d.count })),
maxLevel,
maxLevel
);
const totalSessions = last(filledFunnelRes)?.count ?? 0;
@@ -367,7 +366,7 @@ export class FunnelService {
dropoffPercent: number | null;
previousCount: number;
nextCount: number | null;
}[],
}[]
)
.map((step, index, list) => {
return {
@@ -376,13 +375,15 @@ export class FunnelService {
dropoffPercent: ifNaN(step.dropoffPercent, 0),
isHighestDropoff: (() => {
// Skip if current step has no dropoff
if (!step?.dropoffCount) return false;
if (!step?.dropoffCount) {
return false;
}
// Get maximum dropoff count, excluding 0s
const maxDropoff = Math.max(
...list
.map((s) => s.dropoffCount || 0)
.filter((count) => count > 0),
.filter((count) => count > 0)
);
// Check if this is the first step with the highest dropoff