diff --git a/src/app/(main)/websites/[websiteId]/(reports)/funnels/EventDataFilterRow.tsx b/src/app/(main)/websites/[websiteId]/(reports)/funnels/EventDataFilterRow.tsx
new file mode 100644
index 000000000..f970c62ff
--- /dev/null
+++ b/src/app/(main)/websites/[websiteId]/(reports)/funnels/EventDataFilterRow.tsx
@@ -0,0 +1,252 @@
+import {
+ Button,
+ Column,
+ ComboBox,
+ FormField,
+ Grid,
+ Icon,
+ ListItem,
+ Loading,
+ Select,
+ useDebounce,
+} from '@umami/react-zen';
+import { endOfDay, subMonths } from 'date-fns';
+import { useState } from 'react';
+import { Empty } from '@/components/common/Empty';
+import { useApi, useMessages, useMobile } from '@/components/hooks';
+import { X } from '@/components/icons';
+
+export function getEventDataDateRange() {
+ return {
+ startAt: +subMonths(endOfDay(new Date()), 6),
+ endAt: +endOfDay(new Date()),
+ };
+}
+
+function PropertySelect({
+ websiteId,
+ eventName,
+ value,
+ onChange,
+ onPropertyChange,
+}: {
+ websiteId: string;
+ eventName?: string;
+ value?: string;
+ onChange?: (value: string) => void;
+ onPropertyChange?: (value: string) => void;
+}) {
+ const { get, useQuery } = useApi();
+ const { t, messages } = useMessages();
+ const [search, setSearch] = useState(value ?? '');
+ const searchValue = useDebounce(search, 300);
+ const { startAt, endAt } = getEventDataDateRange();
+
+ const { data, isLoading } = useQuery<
+ Array<{ eventName: string; propertyName: string; total: number }>
+ >({
+ queryKey: ['event-data:properties', { websiteId, eventName, searchValue, startAt, endAt }],
+ queryFn: () =>
+ get(`/websites/${websiteId}/event-data/properties`, {
+ startAt,
+ endAt,
+ ...(eventName ? { event: eventName } : {}),
+ ...(searchValue ? { propertyName: searchValue } : {}),
+ }),
+ enabled: !!websiteId,
+ });
+
+ const properties = [...new Set(data?.map(d => d.propertyName) ?? [])];
+
+ return (
+ {
+ setSearch(v);
+ onChange?.(v);
+ onPropertyChange?.(v);
+ }}
+ formValue="text"
+ allowsEmptyCollection
+ allowsCustomValue
+ renderEmptyState={() =>
+ isLoading ? (
+
+ ) : (
+
+ )
+ }
+ >
+ {properties.map(p => (
+
+ {p}
+
+ ))}
+
+ );
+}
+
+function ValueSelect({
+ websiteId,
+ eventName,
+ propertyName,
+ value,
+ onChange,
+}: {
+ websiteId: string;
+ eventName?: string;
+ propertyName?: string;
+ value?: string;
+ onChange?: (value: string) => void;
+}) {
+ const { get, useQuery } = useApi();
+ const { t, messages } = useMessages();
+ const { startAt, endAt } = getEventDataDateRange();
+
+ const { data, isLoading } = useQuery>({
+ queryKey: ['event-data:values', { websiteId, eventName, propertyName, startAt, endAt }],
+ queryFn: () =>
+ get(`/websites/${websiteId}/event-data/values`, {
+ startAt,
+ endAt,
+ event: eventName,
+ propertyName,
+ }),
+ enabled: !!(websiteId && eventName && propertyName),
+ });
+
+ const values = data?.map(d => d.value) ?? [];
+
+ return (
+ {
+ onChange?.(v);
+ }}
+ formValue="text"
+ allowsEmptyCollection
+ allowsCustomValue
+ renderEmptyState={() =>
+ isLoading ? (
+
+ ) : (
+
+ )
+ }
+ >
+ {values.map(v => (
+
+ {v}
+
+ ))}
+
+ );
+}
+
+function OperatorSelect({
+ value = 'eq',
+ onChange,
+}: {
+ value?: string;
+ onChange?: (value: string) => void;
+}) {
+ const { t, labels } = useMessages();
+ return (
+
+ );
+}
+
+export interface EventDataFilterRowProps {
+ stepIndex: number;
+ filterIndex: number;
+ websiteId: string;
+ eventName: string;
+ initialProperty?: string;
+ onRemove: () => void;
+}
+
+export function EventDataFilterRow({
+ stepIndex,
+ filterIndex,
+ websiteId,
+ eventName,
+ initialProperty,
+ onRemove,
+}: EventDataFilterRowProps) {
+ const { t, labels } = useMessages();
+ const { isMobile } = useMobile();
+ const [propertyName, setPropertyName] = useState(initialProperty ?? '');
+
+ if (isMobile) {
+ return (
+
+
+
+ setPropertyName(v)}
+ />
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ setPropertyName(v)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/(main)/websites/[websiteId]/(reports)/funnels/Funnel.tsx b/src/app/(main)/websites/[websiteId]/(reports)/funnels/Funnel.tsx
index 589831cf5..4d599f70d 100644
--- a/src/app/(main)/websites/[websiteId]/(reports)/funnels/Funnel.tsx
+++ b/src/app/(main)/websites/[websiteId]/(reports)/funnels/Funnel.tsx
@@ -1,4 +1,3 @@
-import { Box, Column, Dialog, Grid, Icon, ProgressBar, Row, Text } from '@umami/react-zen';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { useMessages, useNavigation, useOperatorLabels, useResultQuery } from '@/components/hooks';
import { File, User } from '@/components/icons';
@@ -7,6 +6,7 @@ import { ChangeLabel } from '@/components/metrics/ChangeLabel';
import { Lightning } from '@/components/svg';
import { formatLongNumber } from '@/lib/format';
import type { FunnelResult } from '@/queries/sql/reports/getFunnel';
+import { Box, Column, Grid, Icon, ProgressBar, Row, Text } from '@umami/react-zen';
import { FunnelEditForm } from './FunnelEditForm';
interface FunnelProps {
@@ -17,7 +17,7 @@ interface FunnelProps {
websiteId: string;
}
-export function Funnel({ id, name, type, parameters, websiteId }) {
+export function Funnel({ id, name, type, parameters, websiteId }: FunnelProps) {
const { t, labels } = useMessages();
const { pathname } = useNavigation();
const isSharePage = pathname.includes('/share/');
@@ -41,14 +41,15 @@ export function Funnel({ id, name, type, parameters, websiteId }) {
{!isSharePage && (
-
- {({ close }) => {
- return (
-
- );
- }}
+
+ {({ close }) => }
)}
@@ -100,15 +101,11 @@ export function Funnel({ id, name, type, parameters, websiteId }) {
{filters?.map((f, i) => (
-
- {f.property}
-
-
+ {f.property}
+
{operatorLabels[f.operator] ?? f.operator}
-
- {f.value}
-
+ {f.value}
))}
diff --git a/src/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelAddButton.tsx b/src/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelAddButton.tsx
index 699ff83bf..89cdb789a 100644
--- a/src/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelAddButton.tsx
+++ b/src/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelAddButton.tsx
@@ -1,24 +1,21 @@
-import { Button, Dialog, DialogTrigger, Icon, Modal, Text } from '@umami/react-zen';
import { useMessages } from '@/components/hooks';
import { Plus } from '@/components/icons';
+import { DialogButton } from '@/components/input/DialogButton';
import { FunnelEditForm } from './FunnelEditForm';
export function FunnelAddButton({ websiteId }: { websiteId: string }) {
const { t, labels } = useMessages();
return (
-
-
-
-
-
-
+ }
+ label={t(labels.funnel)}
+ title={t(labels.funnel)}
+ width="700px"
+ height="600px"
+ >
+ {({ close }) => }
+
);
}
diff --git a/src/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelEditForm.tsx b/src/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelEditForm.tsx
index 43c92585d..f8baa5c9e 100644
--- a/src/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelEditForm.tsx
+++ b/src/app/(main)/websites/[websiteId]/(reports)/funnels/FunnelEditForm.tsx
@@ -1,7 +1,6 @@
import {
Button,
Column,
- ComboBox,
Form,
FormButtons,
FormField,
@@ -9,174 +8,21 @@ import {
FormSubmitButton,
Grid,
Icon,
- ListItem,
ListSeparator,
Loading,
Row,
- Select,
Text,
TextField,
- useDebounce,
} from '@umami/react-zen';
-import { endOfDay, subMonths } from 'date-fns';
import { Fragment, useState } from 'react';
-import { Empty } from '@/components/common/Empty';
-import { useApi, useMessages, useReportQuery, useUpdateQuery } from '@/components/hooks';
+import { useApi, useMessages, useMobile, useReportQuery, useUpdateQuery } from '@/components/hooks';
import { Plus, X } from '@/components/icons';
import { ActionSelect } from '@/components/input/ActionSelect';
import { LookupField } from '@/components/input/LookupField';
+import { EventDataFilterRow, getEventDataDateRange } from './EventDataFilterRow';
const FUNNEL_STEPS_MAX = 8;
-function getEventDataDateRange() {
- return {
- startAt: +subMonths(endOfDay(new Date()), 6),
- endAt: +endOfDay(new Date()),
- };
-}
-
-function PropertySelect({
- websiteId,
- eventName,
- value,
- onChange,
- onPropertyChange,
-}: {
- websiteId: string;
- eventName?: string;
- value?: string;
- onChange?: (value: string) => void;
- onPropertyChange?: (value: string) => void;
-}) {
- const { get, useQuery } = useApi();
- const { t, messages } = useMessages();
- const [search, setSearch] = useState(value ?? '');
- const searchValue = useDebounce(search, 300);
- const { startAt, endAt } = getEventDataDateRange();
-
- const { data, isLoading } = useQuery<
- Array<{ eventName: string; propertyName: string; total: number }>
- >({
- queryKey: ['event-data:properties', { websiteId, eventName, searchValue, startAt, endAt }],
- queryFn: () =>
- get(`/websites/${websiteId}/event-data/properties`, {
- startAt,
- endAt,
- ...(eventName ? { event: eventName } : {}),
- ...(searchValue ? { propertyName: searchValue } : {}),
- }),
- enabled: !!websiteId,
- });
-
- const properties = [...new Set(data?.map(d => d.propertyName) ?? [])];
-
- return (
- {
- setSearch(v);
- onChange?.(v);
- onPropertyChange?.(v);
- }}
- formValue="text"
- allowsEmptyCollection
- allowsCustomValue
- renderEmptyState={() =>
- isLoading ? (
-
- ) : (
-
- )
- }
- >
- {properties.map(p => (
-
- {p}
-
- ))}
-
- );
-}
-
-function ValueSelect({
- websiteId,
- eventName,
- propertyName,
- value,
- onChange,
-}: {
- websiteId: string;
- eventName?: string;
- propertyName?: string;
- value?: string;
- onChange?: (value: string) => void;
-}) {
- const { get, useQuery } = useApi();
- const { t, messages } = useMessages();
- const { startAt, endAt } = getEventDataDateRange();
-
- const { data, isLoading } = useQuery>({
- queryKey: ['event-data:values', { websiteId, eventName, propertyName, startAt, endAt }],
- queryFn: () =>
- get(`/websites/${websiteId}/event-data/values`, {
- startAt,
- endAt,
- event: eventName,
- propertyName,
- }),
- enabled: !!(websiteId && eventName && propertyName),
- });
-
- const values = data?.map(d => d.value) ?? [];
-
- return (
- {
- onChange?.(v);
- }}
- formValue="text"
- allowsEmptyCollection
- allowsCustomValue
- renderEmptyState={() =>
- isLoading ? (
-
- ) : (
-
- )
- }
- >
- {values.map(v => (
-
- {v}
-
- ))}
-
- );
-}
-
-function OperatorSelect({
- value = 'eq',
- onChange,
-}: {
- value?: string;
- onChange?: (value: string) => void;
-}) {
- const { t, labels } = useMessages();
- return (
-
- );
-}
-
function StepRow({
index,
websiteId,
@@ -189,39 +35,75 @@ function StepRow({
onRemove: () => void;
}) {
const { t, labels } = useMessages();
+ const { isMobile } = useMobile();
+ const { get, useQuery } = useApi();
const [eventName, setEventName] = useState(initialEventName ?? '');
+ const { startAt, endAt } = getEventDataDateRange();
+
+ const { data: eventProperties } = useQuery>({
+ queryKey: ['event-data:properties', { websiteId, eventName, searchValue: '', startAt, endAt }],
+ queryFn: () =>
+ get(`/websites/${websiteId}/event-data/properties`, {
+ startAt,
+ endAt,
+ ...(eventName ? { event: eventName } : {}),
+ }),
+ enabled: !!websiteId && !!eventName,
+ });
+
+ const hasEventData = (eventProperties?.length ?? 0) > 0;
+
+ const valueField = (
+
+ {({ field, context }) => {
+ const type = context.watch(`steps.${index}.type`);
+ return (
+ {
+ setEventName(v);
+ }}
+ />
+ );
+ }}
+
+ );
return (
-
-
-
-
-
-
-
-
- {({ field, context }) => {
- const type = context.watch(`steps.${index}.type`);
- return (
- {
- setEventName(v);
- }}
- />
- );
- }}
-
-
-
-
+ {isMobile ? (
+
+
+
+
+
+ {valueField}
+
+
+
+ ) : (
+
+
+
+
+
+
+
+ {valueField}
+
+
+
+ )}
{({ fields: filterFields, append: appendFilter, remove: removeFilter, watch }) => {
const stepType = watch(`steps.${index}.type`);
@@ -235,7 +117,7 @@ function StepRow({
{ id: filterId, property: initialProperty }: { id: string; property?: string },
filterIndex: number,
) => (
-
),
)}
-
-
-
+ {hasEventData && (
+
+
+
+ )}
);
}}
@@ -265,51 +149,6 @@ function StepRow({
);
}
-function FilterRow({
- stepIndex,
- filterIndex,
- websiteId,
- eventName,
- initialProperty,
- onRemove,
-}: {
- stepIndex: number;
- filterIndex: number;
- websiteId: string;
- eventName: string;
- initialProperty?: string;
- onRemove: () => void;
-}) {
- const { t, labels } = useMessages();
- const [propertyName, setPropertyName] = useState(initialProperty ?? '');
-
- return (
-
-
- setPropertyName(v)}
- />
-
-
-
-
-
-
-
-
-
- );
-}
-
export function FunnelEditForm({
id,
websiteId,
@@ -322,7 +161,7 @@ export function FunnelEditForm({
onClose?: () => void;
}) {
const { t, labels } = useMessages();
- const { data } = useReportQuery(id);
+ const { data, isLoading } = useReportQuery(id);
const { mutateAsync, error, isPending, touch } = useUpdateQuery(`/reports${id ? `/${id}` : ''}`);
const handleSubmit = async ({
@@ -345,7 +184,7 @@ export function FunnelEditForm({
);
};
- if (id && !data) {
+ if (id && isLoading) {
return ;
}
@@ -405,7 +244,7 @@ export function FunnelEditForm({
);
}}
-
+
diff --git a/src/app/(main)/websites/[websiteId]/(reports)/goals/Goal.tsx b/src/app/(main)/websites/[websiteId]/(reports)/goals/Goal.tsx
index d7553eea5..cebefb555 100644
--- a/src/app/(main)/websites/[websiteId]/(reports)/goals/Goal.tsx
+++ b/src/app/(main)/websites/[websiteId]/(reports)/goals/Goal.tsx
@@ -1,4 +1,4 @@
-import { Column, Dialog, Grid, Icon, ProgressBar, Row, Text } from '@umami/react-zen';
+import { Column, Grid, Icon, ProgressBar, Row, Text } from '@umami/react-zen';
import { LoadingPanel } from '@/components/common/LoadingPanel';
import { useMessages, useNavigation, useResultQuery } from '@/components/hooks';
import { File, User } from '@/components/icons';
@@ -49,18 +49,8 @@ export function Goal({ id, name, type, parameters, websiteId, startDate, endDate
{!isSharePage && (
-
- {({ close }) => {
- return (
-
- );
- }}
+
+ {({ close }) => }
)}
diff --git a/src/app/(main)/websites/[websiteId]/(reports)/goals/GoalAddButton.tsx b/src/app/(main)/websites/[websiteId]/(reports)/goals/GoalAddButton.tsx
index bf42b6560..3e1a42750 100644
--- a/src/app/(main)/websites/[websiteId]/(reports)/goals/GoalAddButton.tsx
+++ b/src/app/(main)/websites/[websiteId]/(reports)/goals/GoalAddButton.tsx
@@ -1,28 +1,21 @@
-import { Button, Dialog, DialogTrigger, Icon, Modal, Text } from '@umami/react-zen';
import { useMessages } from '@/components/hooks';
import { Plus } from '@/components/icons';
+import { DialogButton } from '@/components/input/DialogButton';
import { GoalEditForm } from './GoalEditForm';
export function GoalAddButton({ websiteId }: { websiteId: string }) {
const { t, labels } = useMessages();
return (
-
-
-
-
-
-
+ }
+ label={t(labels.goal)}
+ title={t(labels.goal)}
+ minWidth="400px"
+ minHeight="300px"
+ >
+ {({ close }) => }
+
);
}
diff --git a/src/app/(main)/websites/[websiteId]/(reports)/goals/GoalEditForm.tsx b/src/app/(main)/websites/[websiteId]/(reports)/goals/GoalEditForm.tsx
index 159e43828..3c3b22e52 100644
--- a/src/app/(main)/websites/[websiteId]/(reports)/goals/GoalEditForm.tsx
+++ b/src/app/(main)/websites/[websiteId]/(reports)/goals/GoalEditForm.tsx
@@ -10,7 +10,7 @@ import {
Loading,
TextField,
} from '@umami/react-zen';
-import { useMessages, useReportQuery, useUpdateQuery } from '@/components/hooks';
+import { useMessages, useMobile, useReportQuery, useUpdateQuery } from '@/components/hooks';
import { ActionSelect } from '@/components/input/ActionSelect';
import { LookupField } from '@/components/input/LookupField';
@@ -26,6 +26,7 @@ export function GoalEditForm({
onClose?: () => void;
}) {
const { t, labels } = useMessages();
+ const { isMobile } = useMobile();
const { data } = useReportQuery(id);
const { mutateAsync, error, isPending, touch } = useUpdateQuery(`/reports${id ? `/${id}` : ''}`);
@@ -64,20 +65,33 @@ export function GoalEditForm({
-
-
+ {isMobile ? (
+
-
-
{({ field }) => {
return ;
}}
-
+ ) : (
+
+
+
+
+
+
+
+
+ {({ field }) => {
+ return ;
+ }}
+
+
+
+ )}
diff --git a/src/components/input/DialogButton.tsx b/src/components/input/DialogButton.tsx
index 1cba250f8..6038d733b 100644
--- a/src/components/input/DialogButton.tsx
+++ b/src/components/input/DialogButton.tsx
@@ -18,6 +18,8 @@ export interface DialogButtonProps extends Omit {
height?: string;
minWidth?: string;
minHeight?: string;
+ isOpen?: boolean;
+ onOpenChange?: (isOpen: boolean) => void;
children?: DialogProps['children'];
}
@@ -29,6 +31,8 @@ export function DialogButton({
height,
minWidth,
minHeight,
+ isOpen,
+ onOpenChange,
children,
...props
}: DialogButtonProps) {
@@ -39,29 +43,44 @@ export function DialogButton({
minWidth,
minHeight,
maxHeight: 'calc(100dvh - 40px)',
+ overflowY: 'auto',
padding: '32px',
};
if (isMobile) {
style.width = '100%';
style.height = '100%';
+ style.minWidth = undefined;
+ style.minHeight = undefined;
style.maxHeight = '100%';
style.overflowY = 'auto';
}
+ const dialog = (
+
+ );
+
+ if (isOpen !== undefined) {
+ return (
+
+ {dialog}
+
+ );
+ }
+
return (
-
+ {dialog}
);
diff --git a/src/components/input/FilterEditForm.tsx b/src/components/input/FilterEditForm.tsx
index 5e7e6020f..662be24a0 100644
--- a/src/components/input/FilterEditForm.tsx
+++ b/src/components/input/FilterEditForm.tsx
@@ -112,7 +112,7 @@ export function FilterEditForm({ websiteId, onChange, onClose }: FilterEditFormP
-
+
diff --git a/src/components/input/LookupField.tsx b/src/components/input/LookupField.tsx
index 65dc730a1..daa95aa0c 100644
--- a/src/components/input/LookupField.tsx
+++ b/src/components/input/LookupField.tsx
@@ -1,22 +1,25 @@
+import { Empty } from '@/components/common/Empty';
+import { useMessages, useWebsiteValuesQuery } from '@/components/hooks';
import { ComboBox, type ComboBoxProps, ListItem, Loading, useDebounce } from '@umami/react-zen';
import { endOfDay, subMonths } from 'date-fns';
import { type SetStateAction, useMemo, useState } from 'react';
-import { Empty } from '@/components/common/Empty';
-import { useMessages, useWebsiteValuesQuery } from '@/components/hooks';
-export interface LookupFieldProps extends ComboBoxProps {
+export interface LookupFieldProps extends Omit {
websiteId: string;
type: string;
value: string;
onChange: (value: string) => void;
- /**
- * `FormField` injects `onChange` via `cloneElement`, overwriting any custom handler.
- * Use `onValueChange` for side effects that must survive that override.
- */
onValueChange?: (value: string) => void;
}
-export function LookupField({ websiteId, type, value, onChange, onValueChange, ...props }: LookupFieldProps) {
+export function LookupField({
+ websiteId,
+ type,
+ value,
+ onChange,
+ onValueChange,
+ ...props
+}: LookupFieldProps) {
const { t, messages } = useMessages();
const [search, setSearch] = useState(value);
const searchValue = useDebounce(search, 300);
diff --git a/src/components/input/ReportEditButton.tsx b/src/components/input/ReportEditButton.tsx
index f61899277..777b75d5f 100644
--- a/src/components/input/ReportEditButton.tsx
+++ b/src/components/input/ReportEditButton.tsx
@@ -14,17 +14,28 @@ import { type ReactNode, useState } from 'react';
import { useMessages } from '@/components/hooks';
import { useDeleteQuery } from '@/components/hooks/queries/useDeleteQuery';
import { Edit, MoreHorizontal, Trash } from '@/components/icons';
+import { DialogButton } from './DialogButton';
export function ReportEditButton({
id,
name,
type,
+ title,
+ width,
+ height,
+ minWidth,
+ minHeight,
children,
onDelete,
}: {
id: string;
name: string;
type: string;
+ title?: ReactNode;
+ width?: string;
+ height?: string;
+ minWidth?: string;
+ minHeight?: string;
onDelete?: () => void;
children: ({ close }: { close: () => void }) => ReactNode;
}) {
@@ -81,18 +92,26 @@ export function ReportEditButton({
-
- {showEdit && children({ close: handleClose })}
- {showDelete && (
-
- {t(messages.confirmDelete, { target: name })}
-
- )}
+ !open && handleClose()}
+ title={title}
+ width={width}
+ height={height}
+ minWidth={minWidth}
+ minHeight={minHeight}
+ >
+ {children}
+
+ !open && handleClose()}>
+
+ {t(messages.confirmDelete, { target: name })}
+
>
);
diff --git a/src/queries/sql/reports/getFunnel.ts b/src/queries/sql/reports/getFunnel.ts
index fcf30a903..b1b55c2b3 100644
--- a/src/queries/sql/reports/getFunnel.ts
+++ b/src/queries/sql/reports/getFunnel.ts
@@ -84,6 +84,7 @@ async function relationalQuery(
select 1 from event_data _ed${stepIndex}_${fi}
where _ed${stepIndex}_${fi}.website_event_id = ${eventAlias}.event_id
and _ed${stepIndex}_${fi}.website_id = {{websiteId::uuid}}
+ and _ed${stepIndex}_${fi}.created_at between {{startDate}} and {{endDate}}
and _ed${stepIndex}_${fi}.data_key = {{${keyParam}}}
and case when _ed${stepIndex}_${fi}.data_type = 2 then replace(_ed${stepIndex}_${fi}.string_value, '.0000', '') else _ed${stepIndex}_${fi}.string_value end ${op} {{${valParam}}}
)`;
@@ -210,6 +211,7 @@ async function clickhouseQuery(
stepIndex: number,
stepFilters: Array | undefined,
params: Record,
+ eventAlias: string,
): string {
if (!stepFilters?.length) return '';
@@ -231,9 +233,10 @@ async function clickhouseQuery(
}
params[valParam] = val;
- return `and event_id in (
+ return `and ${eventAlias}.event_id in (
select event_id from event_data
where website_id = {websiteId:UUID}
+ and created_at between {startDate:DateTime64} and {endDate:DateTime64}
and data_key = {${keyParam}:String}
and multiIf(data_type = 2, replaceAll(string_value, '.0000', ''), string_value) ${op} {${valParam}:String}
)`;
@@ -269,8 +272,11 @@ async function clickhouseQuery(
paramValue = cv.value.replace(/^\*|\*$/g, '%');
}
+ const eventAlias = levelNumber === 1 ? 'level0' : 'y';
const eventDataClause =
- !isURL && cv.filters?.length ? buildEventDataFilters(i, cv.filters, extraParams) : '';
+ !isURL && cv.filters?.length
+ ? buildEventDataFilters(i, cv.filters, extraParams, eventAlias)
+ : '';
if (levelNumber === 1) {
pv.levelOneQuery = `\n